mirror of
https://github.com/chylex/TweetDuck.git
synced 2025-09-14 10:32:10 +02:00
Compare commits
86 Commits
Author | SHA1 | Date | |
---|---|---|---|
00acc677e6 | |||
1a799881e8 | |||
f75677593a | |||
19e3bd19f0 | |||
85701b0a3c | |||
014cb18dcb | |||
e71e1c853f | |||
ee9d9196f5 | |||
53c8272e01 | |||
7f7b6b1e2a | |||
405777e0f5 | |||
df2b624cb5 | |||
8a48d5c2f9 | |||
c55ee71442 | |||
3f82745f5b | |||
404187a1ae | |||
2b7b3f586b | |||
04959a3493 | |||
97cf4932ae | |||
b0d88a0a37 | |||
67a2e40622 | |||
3a28556c7f | |||
9ecc92b9a5 | |||
ca023be98a | |||
11a1423f76 | |||
79f6df121b | |||
71eade7e86 | |||
5f81d29036 | |||
ec1cb5dc5f | |||
fd969e2d55 | |||
37e33b77ff | |||
f7ed7703b4 | |||
4bb35295ca | |||
1e4f673f9e | |||
7cadb1c403 | |||
37148f5093 | |||
f6bc26789f | |||
b3f5a88525 | |||
1e538d2b28 | |||
7d7bfb7b01 | |||
41d86ba440 | |||
3df474a8a5 | |||
a50d6e8f47 | |||
6081e5b9c1 | |||
66ccea920c | |||
470d63093f | |||
eae0507831 | |||
92af85d3bb | |||
7635af5730 | |||
a838e89695 | |||
b22289a8b9 | |||
45b3ff52c6 | |||
4464991f4c | |||
b0d2f77583 | |||
b211a4405d | |||
8823016d2c | |||
859fdc7ec1 | |||
028d5ed01f | |||
5fd5a2a436 | |||
79a7e7470c | |||
9ecef78aed | |||
65a837a6e1 | |||
6e4db4acea | |||
26fb977d05 | |||
b42cd1c048 | |||
467f7cd12f | |||
66699ce9df | |||
cf7d903932 | |||
a7ab67925c | |||
a474ba4260 | |||
09e5636e86 | |||
2295a875be | |||
82a2455afc | |||
268de676ee | |||
8fe26c07f1 | |||
da3921b1ca | |||
4dd2e787d1 | |||
ce005ae6c2 | |||
1513f46a11 | |||
7543eeb0f4 | |||
873242120c | |||
98f8095a65 | |||
785571a550 | |||
0c4bd4044e | |||
0319543dce | |||
82d70b2d7f |
@@ -1,7 +1,8 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using TweetDck.Core.Utils;
|
||||
|
||||
namespace TweetDck.Configuration{
|
||||
sealed class LockManager{
|
||||
@@ -114,25 +115,45 @@ namespace TweetDck.Configuration{
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool CloseLockingProcess(int timeout){
|
||||
public bool CloseLockingProcess(int closeTimeout, int killTimeout){
|
||||
if (LockingProcess != null){
|
||||
LockingProcess.CloseMainWindow();
|
||||
try{
|
||||
if (LockingProcess.CloseMainWindow()){
|
||||
WindowsUtils.TrySleepUntil(CheckLockingProcessExited, closeTimeout, 250);
|
||||
}
|
||||
|
||||
for(int waited = 0; waited < timeout && !LockingProcess.HasExited; waited += 250){
|
||||
LockingProcess.Refresh();
|
||||
Thread.Sleep(250);
|
||||
}
|
||||
if (!LockingProcess.HasExited){
|
||||
LockingProcess.Kill();
|
||||
WindowsUtils.TrySleepUntil(CheckLockingProcessExited, killTimeout, 250);
|
||||
}
|
||||
|
||||
if (LockingProcess.HasExited){
|
||||
LockingProcess.Dispose();
|
||||
LockingProcess = null;
|
||||
return true;
|
||||
if (LockingProcess.HasExited){
|
||||
LockingProcess.Dispose();
|
||||
LockingProcess = null;
|
||||
return true;
|
||||
}
|
||||
}catch(Exception ex){
|
||||
if (ex is InvalidOperationException || ex is Win32Exception){
|
||||
if (LockingProcess != null){
|
||||
LockingProcess.Refresh();
|
||||
|
||||
bool hasExited = LockingProcess.HasExited;
|
||||
LockingProcess.Dispose();
|
||||
return hasExited;
|
||||
}
|
||||
}
|
||||
else throw;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool CheckLockingProcessExited(){
|
||||
LockingProcess.Refresh();
|
||||
return LockingProcess.HasExited;
|
||||
}
|
||||
|
||||
// Utility functions
|
||||
|
||||
private static void WriteIntToStream(Stream stream, int value){
|
||||
|
@@ -67,7 +67,7 @@ namespace TweetDck.Configuration{
|
||||
|
||||
public string NotificationSoundPath{
|
||||
get{
|
||||
return !string.IsNullOrEmpty(notificationSoundPath) && File.Exists(notificationSoundPath) ? notificationSoundPath : string.Empty;
|
||||
return string.IsNullOrEmpty(notificationSoundPath) ? string.Empty : notificationSoundPath;
|
||||
}
|
||||
|
||||
set{
|
||||
|
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<package id="cef.redist.x64" version="3.2785.1486" targetFramework="net452" />
|
||||
<package id="cef.redist.x86" version="3.2785.1486" targetFramework="net452" />
|
||||
<package id="CefSharp.Common" version="53.0.1" targetFramework="net452" />
|
||||
<package id="CefSharp.WinForms" version="53.0.1" targetFramework="net452" />
|
||||
<package id="Microsoft.VC120.CRT.JetBrains" version="12.0.21005.2" targetFramework="net452" />
|
||||
<package id="cef.redist.x64" version="3.2883.1552" targetFramework="net452" xmlns="" />
|
||||
<package id="cef.redist.x86" version="3.2883.1552" targetFramework="net452" xmlns="" />
|
||||
<package id="CefSharp.Common" version="55.0.0" targetFramework="net452" xmlns="" />
|
||||
<package id="CefSharp.WinForms" version="55.0.0" targetFramework="net452" xmlns="" />
|
||||
<package id="Microsoft.VC120.CRT.JetBrains" version="12.0.21005.2" targetFramework="net452" xmlns="" />
|
||||
</packages>
|
34
Core/Bridge/PropertyBridge.cs
Normal file
34
Core/Bridge/PropertyBridge.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace TweetDck.Core.Bridge{
|
||||
static class PropertyBridge{
|
||||
[Flags]
|
||||
public enum Properties{
|
||||
ExpandLinksOnHover = 1,
|
||||
MuteNotifications = 2,
|
||||
HasCustomNotificationSound = 4,
|
||||
All = ExpandLinksOnHover | MuteNotifications | HasCustomNotificationSound
|
||||
}
|
||||
|
||||
public static string GenerateScript(Properties properties = Properties.All){
|
||||
StringBuilder build = new StringBuilder();
|
||||
build.Append("(function(c){");
|
||||
|
||||
if (properties.HasFlag(Properties.ExpandLinksOnHover)){
|
||||
build.Append("c.expandLinksOnHover=").Append(Program.UserConfig.ExpandLinksOnHover ? "true;" : "false;");
|
||||
}
|
||||
|
||||
if (properties.HasFlag(Properties.MuteNotifications)){
|
||||
build.Append("c.muteNotifications=").Append(Program.UserConfig.MuteNotifications ? "true;" : "false;");
|
||||
}
|
||||
|
||||
if (properties.HasFlag(Properties.HasCustomNotificationSound)){
|
||||
build.Append("c.hasCustomNotificationSound=").Append(Program.UserConfig.NotificationSoundPath.Length > 0 ? "true;" : "false;");
|
||||
}
|
||||
|
||||
build.Append("})(window.$TDX=window.$TDX||{})");
|
||||
return build.ToString();
|
||||
}
|
||||
}
|
||||
}
|
@@ -21,97 +21,55 @@ namespace TweetDck.Core.Bridge{
|
||||
private readonly FormBrowser form;
|
||||
private readonly FormNotification notification;
|
||||
|
||||
public string BrandName{
|
||||
get{
|
||||
return Program.BrandName;
|
||||
}
|
||||
}
|
||||
|
||||
public string VersionTag{
|
||||
get{
|
||||
return Program.VersionTag;
|
||||
}
|
||||
}
|
||||
|
||||
public bool MuteNotifications{
|
||||
get{
|
||||
return Program.UserConfig.MuteNotifications;
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasCustomNotificationSound{
|
||||
get{
|
||||
return !string.IsNullOrEmpty(Program.UserConfig.NotificationSoundPath);
|
||||
}
|
||||
}
|
||||
|
||||
public bool ExpandLinksOnHover{
|
||||
get{
|
||||
return Program.UserConfig.ExpandLinksOnHover;
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasCustomBrowserCSS{
|
||||
get{
|
||||
return !string.IsNullOrEmpty(Program.UserConfig.CustomBrowserCSS);
|
||||
}
|
||||
}
|
||||
|
||||
public string CustomBrowserCSS{
|
||||
get{
|
||||
return Program.UserConfig.CustomBrowserCSS;
|
||||
}
|
||||
}
|
||||
|
||||
public TweetDeckBridge(FormBrowser form, FormNotification notification){
|
||||
this.form = form;
|
||||
this.notification = notification;
|
||||
}
|
||||
|
||||
public void LoadFontSizeClass(string fsClass){
|
||||
form.InvokeSafe(() => {
|
||||
form.InvokeAsyncSafe(() => {
|
||||
TweetNotification.SetFontSizeClass(fsClass);
|
||||
});
|
||||
}
|
||||
|
||||
public void LoadNotificationHeadContents(string headContents){
|
||||
form.InvokeSafe(() => {
|
||||
form.InvokeAsyncSafe(() => {
|
||||
TweetNotification.SetHeadTag(headContents);
|
||||
});
|
||||
}
|
||||
|
||||
public void SetLastRightClickedLink(string link){
|
||||
form.InvokeSafe(() => LastRightClickedLink = link);
|
||||
form.InvokeAsyncSafe(() => LastRightClickedLink = link);
|
||||
}
|
||||
|
||||
public void SetLastHighlightedTweet(string link, string quotedLink){
|
||||
form.InvokeSafe(() => {
|
||||
form.InvokeAsyncSafe(() => {
|
||||
LastHighlightedTweet = link;
|
||||
LastHighlightedQuotedTweet = quotedLink;
|
||||
});
|
||||
}
|
||||
|
||||
public void SetNotificationQuotedTweet(string link){
|
||||
notification.InvokeSafe(() => notification.CurrentQuotedTweetUrl = link);
|
||||
notification.InvokeAsyncSafe(() => notification.CurrentQuotedTweetUrl = link);
|
||||
}
|
||||
|
||||
public void OpenSettingsMenu(){
|
||||
form.InvokeSafe(form.OpenSettings);
|
||||
form.InvokeAsyncSafe(form.OpenSettings);
|
||||
}
|
||||
|
||||
public void OpenPluginsMenu(){
|
||||
form.InvokeSafe(form.OpenPlugins);
|
||||
form.InvokeAsyncSafe(form.OpenPlugins);
|
||||
}
|
||||
|
||||
public void OnTweetPopup(string tweetHtml, string tweetUrl, int tweetCharacters){
|
||||
notification.InvokeSafe(() => {
|
||||
notification.InvokeAsyncSafe(() => {
|
||||
form.OnTweetNotification();
|
||||
notification.ShowNotification(new TweetNotification(tweetHtml, tweetUrl, tweetCharacters));
|
||||
});
|
||||
}
|
||||
|
||||
public void OnTweetSound(){
|
||||
form.InvokeSafe(() => {
|
||||
form.InvokeAsyncSafe(() => {
|
||||
form.OnTweetNotification();
|
||||
form.PlayNotificationSound();
|
||||
});
|
||||
@@ -119,15 +77,15 @@ namespace TweetDck.Core.Bridge{
|
||||
|
||||
public void DisplayTooltip(string text, bool showInNotification){
|
||||
if (showInNotification){
|
||||
notification.InvokeSafe(() => notification.DisplayTooltip(text));
|
||||
notification.InvokeAsyncSafe(() => notification.DisplayTooltip(text));
|
||||
}
|
||||
else{
|
||||
form.InvokeSafe(() => form.DisplayTooltip(text));
|
||||
form.InvokeAsyncSafe(() => form.DisplayTooltip(text));
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadNextNotification(){
|
||||
notification.InvokeSafe(notification.FinishCurrentTweet);
|
||||
notification.InvokeAsyncSafe(notification.FinishCurrentTweet);
|
||||
}
|
||||
|
||||
public void TryPasteImage(){
|
||||
@@ -151,23 +109,15 @@ namespace TweetDck.Core.Bridge{
|
||||
}
|
||||
|
||||
public void ClickUploadImage(int offsetX, int offsetY){
|
||||
form.InvokeSafe(() => {
|
||||
Point prevPos = Cursor.Position;
|
||||
|
||||
Cursor.Position = form.PointToScreen(new Point(offsetX, offsetY));
|
||||
NativeMethods.SimulateMouseClick(NativeMethods.MouseButton.Left);
|
||||
Cursor.Position = prevPos;
|
||||
|
||||
form.OnImagePastedFinish();
|
||||
});
|
||||
form.InvokeAsyncSafe(() => form.TriggerImageUpload(offsetX, offsetY));
|
||||
}
|
||||
|
||||
public void ScreenshotTweet(string html, int width, int height){
|
||||
form.InvokeSafe(() => form.OnTweetScreenshotReady(html, width, height));
|
||||
form.InvokeAsyncSafe(() => form.OnTweetScreenshotReady(html, width, height));
|
||||
}
|
||||
|
||||
public void FixClipboard(){
|
||||
form.InvokeSafe(WindowsUtils.ClipboardStripHtmlStyles);
|
||||
form.InvokeAsyncSafe(WindowsUtils.ClipboardStripHtmlStyles);
|
||||
}
|
||||
|
||||
public void OpenBrowser(string url){
|
||||
|
@@ -16,6 +16,10 @@ namespace TweetDck.Core.Controls{
|
||||
}
|
||||
}
|
||||
|
||||
public static void InvokeAsyncSafe(this Control control, Action func){
|
||||
control.BeginInvoke(func);
|
||||
}
|
||||
|
||||
public static void MoveToCenter(this Form targetForm, Form parentForm){
|
||||
targetForm.Location = new Point(parentForm.Location.X+parentForm.Width/2-targetForm.Width/2, parentForm.Location.Y+parentForm.Height/2-targetForm.Height/2);
|
||||
}
|
||||
|
@@ -13,10 +13,11 @@ using TweetDck.Updates;
|
||||
using TweetDck.Plugins;
|
||||
using TweetDck.Plugins.Enums;
|
||||
using TweetDck.Plugins.Events;
|
||||
using System.Media;
|
||||
using TweetDck.Core.Bridge;
|
||||
using TweetDck.Core.Notification;
|
||||
using TweetDck.Core.Notification.Screenshot;
|
||||
using TweetDck.Updates.Events;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace TweetDck.Core{
|
||||
sealed partial class FormBrowser : Form{
|
||||
@@ -41,7 +42,7 @@ namespace TweetDck.Core{
|
||||
private FormWindowState prevState;
|
||||
|
||||
private TweetScreenshotManager notificationScreenshotManager;
|
||||
private SoundPlayer notificationSound;
|
||||
private SoundNotification soundNotification;
|
||||
|
||||
public FormBrowser(PluginManager pluginManager, UpdaterSettings updaterSettings){
|
||||
InitializeComponent();
|
||||
@@ -53,7 +54,11 @@ namespace TweetDck.Core{
|
||||
this.plugins.PluginChangedState += plugins_PluginChangedState;
|
||||
|
||||
this.notification = CreateNotificationForm(NotificationFlags.AutoHide | NotificationFlags.TopMost);
|
||||
#if DEBUG
|
||||
this.notification.CanMoveWindow = () => (ModifierKeys & Keys.Alt) == Keys.Alt;
|
||||
#else
|
||||
this.notification.CanMoveWindow = () => false;
|
||||
#endif
|
||||
this.notification.Show();
|
||||
|
||||
this.browser = new ChromiumWebBrowser("https://tweetdeck.twitter.com/"){
|
||||
@@ -69,11 +74,13 @@ namespace TweetDck.Core{
|
||||
|
||||
this.browser.LoadingStateChanged += Browser_LoadingStateChanged;
|
||||
this.browser.FrameLoadEnd += Browser_FrameLoadEnd;
|
||||
this.browser.RegisterJsObject("$TD", new TweetDeckBridge(this, notification));
|
||||
this.browser.RegisterAsyncJsObject("$TD", new TweetDeckBridge(this, notification));
|
||||
this.browser.RegisterAsyncJsObject("$TDP", plugins.Bridge);
|
||||
|
||||
Controls.Add(browser);
|
||||
|
||||
Controls.Add(new MenuStrip{ Visible = false }); // fixes Alt freezing the program in Win 10 Anniversary Update
|
||||
|
||||
Disposed += (sender, args) => {
|
||||
browser.Dispose();
|
||||
|
||||
@@ -81,8 +88,8 @@ namespace TweetDck.Core{
|
||||
notificationScreenshotManager.Dispose();
|
||||
}
|
||||
|
||||
if (notificationSound != null){
|
||||
notificationSound.Dispose();
|
||||
if (soundNotification != null){
|
||||
soundNotification.Dispose();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -92,13 +99,16 @@ namespace TweetDck.Core{
|
||||
|
||||
UpdateTrayIcon();
|
||||
|
||||
Config.MuteToggled += Config_MuteToggled;
|
||||
|
||||
this.updates = new UpdateHandler(browser, this, updaterSettings);
|
||||
this.updates.UpdateAccepted += updates_UpdateAccepted;
|
||||
this.updates.UpdateDismissed += updates_UpdateDismissed;
|
||||
}
|
||||
|
||||
private void ShowChildForm(Form form){
|
||||
form.VisibleChanged += (sender, args) => form.MoveToCenter(this);
|
||||
form.Show(this);
|
||||
form.Shown += (sender, args) => form.MoveToCenter(this);
|
||||
}
|
||||
|
||||
public void ForceClose(){
|
||||
@@ -135,7 +145,9 @@ namespace TweetDck.Core{
|
||||
|
||||
private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
||||
if (e.Frame.IsMain && BrowserUtils.IsTweetDeckWebsite(e.Frame)){
|
||||
UpdateProperties();
|
||||
ScriptLoader.ExecuteFile(e.Frame, "code.js");
|
||||
ReinjectCustomCSS(Config.CustomBrowserCSS);
|
||||
|
||||
#if DEBUG
|
||||
ScriptLoader.ExecuteFile(e.Frame, "debug.js");
|
||||
@@ -192,6 +204,10 @@ namespace TweetDck.Core{
|
||||
}
|
||||
}
|
||||
|
||||
private void Config_MuteToggled(object sender, EventArgs e){
|
||||
UpdateProperties(PropertyBridge.Properties.MuteNotifications);
|
||||
}
|
||||
|
||||
private void Config_TrayBehaviorChanged(object sender, EventArgs e){
|
||||
if (!isLoaded)return;
|
||||
|
||||
@@ -215,7 +231,7 @@ namespace TweetDck.Core{
|
||||
}
|
||||
|
||||
private void plugins_Reloaded(object sender, PluginLoadEventArgs e){
|
||||
ReloadBrowser();
|
||||
browser.GetBrowser().Reload();
|
||||
}
|
||||
|
||||
private void plugins_PluginChangedState(object sender, PluginChangedStateEventArgs e){
|
||||
@@ -228,6 +244,7 @@ namespace TweetDck.Core{
|
||||
FormUpdateDownload downloadForm = new FormUpdateDownload(e.UpdateInfo);
|
||||
downloadForm.MoveToCenter(this);
|
||||
downloadForm.ShowDialog();
|
||||
downloadForm.Dispose();
|
||||
|
||||
if (downloadForm.UpdateStatus == FormUpdateDownload.Status.Succeeded){
|
||||
UpdateInstallerPath = downloadForm.InstallerPath;
|
||||
@@ -241,9 +258,19 @@ namespace TweetDck.Core{
|
||||
}
|
||||
}
|
||||
|
||||
private void updates_UpdateDismissed(object sender, UpdateDismissedEventArgs e){
|
||||
Config.DismissedUpdate = e.VersionTag;
|
||||
Config.Save();
|
||||
}
|
||||
|
||||
protected override void WndProc(ref Message m){
|
||||
if (isLoaded && m.Msg == Program.WindowRestoreMessage){
|
||||
trayIcon_ClickRestore(trayIcon, new EventArgs());
|
||||
using(Process process = Process.GetCurrentProcess()){
|
||||
if (process.Id == m.WParam.ToInt32()){
|
||||
trayIcon_ClickRestore(trayIcon, new EventArgs());
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -269,29 +296,47 @@ namespace TweetDck.Core{
|
||||
notification.ResumeNotification();
|
||||
}
|
||||
|
||||
// javascript calls
|
||||
|
||||
public void ReinjectCustomCSS(string css){
|
||||
browser.ExecuteScriptAsync("TDGF_reinjectCustomCSS", css == null ? string.Empty : css.Replace(Environment.NewLine, " "));
|
||||
}
|
||||
|
||||
public void UpdateProperties(PropertyBridge.Properties properties = PropertyBridge.Properties.All){
|
||||
browser.ExecuteScriptAsync(PropertyBridge.GenerateScript(properties));
|
||||
}
|
||||
|
||||
// callback handlers
|
||||
|
||||
public void OpenSettings(){
|
||||
OpenSettings(0);
|
||||
}
|
||||
|
||||
public void OpenSettings(int tabIndex){
|
||||
if (currentFormSettings != null){
|
||||
currentFormSettings.BringToFront();
|
||||
}
|
||||
else{
|
||||
bool prevEnableUpdateCheck = Config.EnableUpdateCheck;
|
||||
|
||||
currentFormSettings = new FormSettings(this, plugins, updates);
|
||||
currentFormSettings = new FormSettings(this, plugins, updates, tabIndex);
|
||||
|
||||
currentFormSettings.FormClosed += (sender, args) => {
|
||||
currentFormSettings = null;
|
||||
|
||||
if (!prevEnableUpdateCheck && Config.EnableUpdateCheck){
|
||||
updates.Settings.DismissedUpdate = string.Empty;
|
||||
Config.DismissedUpdate = string.Empty;
|
||||
Config.Save();
|
||||
|
||||
updates.Check(false);
|
||||
}
|
||||
|
||||
if (!Config.EnableTrayHighlight){
|
||||
trayIcon.HasNotifications = false;
|
||||
}
|
||||
|
||||
UpdateProperties(PropertyBridge.Properties.ExpandLinksOnHover | PropertyBridge.Properties.HasCustomNotificationSound);
|
||||
};
|
||||
|
||||
ShowChildForm(currentFormSettings);
|
||||
@@ -327,19 +372,15 @@ namespace TweetDck.Core{
|
||||
}
|
||||
|
||||
public void PlayNotificationSound(){
|
||||
if (string.IsNullOrEmpty(Config.NotificationSoundPath)){
|
||||
if (Config.NotificationSoundPath.Length == 0){
|
||||
return;
|
||||
}
|
||||
|
||||
if (notificationSound == null){
|
||||
notificationSound = new SoundPlayer();
|
||||
if (soundNotification == null){
|
||||
soundNotification = new SoundNotification(this);
|
||||
}
|
||||
|
||||
if (notificationSound.SoundLocation != Config.NotificationSoundPath){
|
||||
notificationSound.SoundLocation = Config.NotificationSoundPath;
|
||||
}
|
||||
|
||||
notificationSound.Play();
|
||||
soundNotification.Play(Config.NotificationSoundPath);
|
||||
}
|
||||
|
||||
public void OnTweetScreenshotReady(string html, int width, int height){
|
||||
@@ -365,16 +406,14 @@ namespace TweetDck.Core{
|
||||
browser.ExecuteScriptAsync("TDGF_tryPasteImage()");
|
||||
}
|
||||
|
||||
public void OnImagePastedFinish(){
|
||||
browser.ExecuteScriptAsync("TDGF_tryPasteImageFinish()");
|
||||
public void TriggerImageUpload(int offsetX, int offsetY){
|
||||
IBrowserHost host = browser.GetBrowser().GetHost();
|
||||
host.SendMouseClickEvent(offsetX, offsetY, MouseButtonType.Left, false, 1, CefEventFlags.None);
|
||||
host.SendMouseClickEvent(offsetX, offsetY, MouseButtonType.Left, true, 1, CefEventFlags.None);
|
||||
}
|
||||
|
||||
public void TriggerTweetScreenshot(){
|
||||
browser.ExecuteScriptAsync("TDGF_triggerScreenshot()");
|
||||
}
|
||||
|
||||
public void ReloadBrowser(){
|
||||
browser.ExecuteScriptAsync("window.location.reload()");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -133,7 +133,7 @@ namespace TweetDck.Core{
|
||||
|
||||
if (!flags.HasFlag(NotificationFlags.DisableScripts)){
|
||||
notificationJS = ScriptLoader.LoadResource(NotificationScriptFile);
|
||||
browser.RegisterJsObject("$TD", new TweetDeckBridge(owner, this));
|
||||
browser.RegisterAsyncJsObject("$TD", new TweetDeckBridge(owner, this));
|
||||
|
||||
if (plugins != null){
|
||||
pluginJS = ScriptLoader.LoadResource(PluginManager.PluginNotificationScriptFile);
|
||||
@@ -238,6 +238,7 @@ namespace TweetDck.Core{
|
||||
|
||||
private void Browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
||||
if (e.Frame.IsMain && notificationJS != null && browser.Address != "about:blank" && !flags.HasFlag(NotificationFlags.DisableScripts)){
|
||||
e.Frame.ExecuteJavaScriptAsync(PropertyBridge.GenerateScript(PropertyBridge.Properties.ExpandLinksOnHover));
|
||||
ScriptLoader.ExecuteScript(e.Frame, notificationJS, NotificationScriptIdentifier);
|
||||
|
||||
if (plugins != null && plugins.HasAnyPlugin(PluginEnvironment.Notification)){
|
||||
@@ -435,7 +436,7 @@ namespace TweetDck.Core{
|
||||
else{
|
||||
Point position = PointToClient(Cursor.Position);
|
||||
position.Offset(20, 5);
|
||||
toolTip.Show(text, this, position);
|
||||
toolTip.Show(text, this, position); // TODO figure out flickering when moving the mouse
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@ using System;
|
||||
using System.IO;
|
||||
using System.Windows.Forms;
|
||||
using TweetDck.Core.Bridge;
|
||||
using TweetDck.Core.Controls;
|
||||
using TweetDck.Core.Utils;
|
||||
|
||||
namespace TweetDck.Core.Handling{
|
||||
@@ -21,6 +22,12 @@ namespace TweetDck.Core.Handling{
|
||||
}
|
||||
#endif
|
||||
|
||||
private readonly Form form;
|
||||
|
||||
protected ContextMenuBase(Form form){
|
||||
this.form = form;
|
||||
}
|
||||
|
||||
public virtual void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
|
||||
if (parameters.TypeFlags.HasFlag(ContextMenuType.Link) && !parameters.UnfilteredLinkUrl.EndsWith("tweetdeck.twitter.com/#", StringComparison.Ordinal)){
|
||||
model.AddItem((CefMenuCommand)MenuOpenLinkUrl, "Open link in browser");
|
||||
@@ -43,7 +50,7 @@ namespace TweetDck.Core.Handling{
|
||||
break;
|
||||
|
||||
case MenuCopyLinkUrl:
|
||||
Clipboard.SetText(string.IsNullOrEmpty(TweetDeckBridge.LastRightClickedLink) ? parameters.UnfilteredLinkUrl : TweetDeckBridge.LastRightClickedLink, TextDataFormat.UnicodeText);
|
||||
SetClipboardText(string.IsNullOrEmpty(TweetDeckBridge.LastRightClickedLink) ? parameters.UnfilteredLinkUrl : TweetDeckBridge.LastRightClickedLink);
|
||||
break;
|
||||
|
||||
case MenuOpenImage:
|
||||
@@ -74,7 +81,7 @@ namespace TweetDck.Core.Handling{
|
||||
break;
|
||||
|
||||
case MenuCopyImageUrl:
|
||||
Clipboard.SetText(parameters.SourceUrl, TextDataFormat.UnicodeText);
|
||||
SetClipboardText(parameters.SourceUrl);
|
||||
break;
|
||||
|
||||
#if DEBUG
|
||||
@@ -93,6 +100,10 @@ namespace TweetDck.Core.Handling{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void SetClipboardText(string text){
|
||||
form.InvokeAsyncSafe(() => WindowsUtils.SetClipboard(text, TextDataFormat.UnicodeText));
|
||||
}
|
||||
|
||||
protected static void RemoveSeparatorIfLast(IMenuModel model){
|
||||
if (model.Count > 0 && model.GetTypeAt(model.Count-1) == MenuItemType.Separator){
|
||||
model.RemoveAt(model.Count-1);
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using CefSharp;
|
||||
using System.Windows.Forms;
|
||||
using TweetDck.Core.Bridge;
|
||||
using TweetDck.Core.Controls;
|
||||
using TweetDck.Core.Utils;
|
||||
@@ -23,7 +22,7 @@ namespace TweetDck.Core.Handling{
|
||||
private string lastHighlightedTweet;
|
||||
private string lastHighlightedQuotedTweet;
|
||||
|
||||
public ContextMenuBrowser(FormBrowser form){
|
||||
public ContextMenuBrowser(FormBrowser form) : base(form){
|
||||
this.form = form;
|
||||
}
|
||||
|
||||
@@ -34,6 +33,10 @@ namespace TweetDck.Core.Handling{
|
||||
model.Remove(CefMenuCommand.ViewSource);
|
||||
RemoveSeparatorIfLast(model);
|
||||
|
||||
if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection)){
|
||||
model.AddSeparator();
|
||||
}
|
||||
|
||||
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
||||
|
||||
lastHighlightedTweet = TweetDeckBridge.LastHighlightedTweet;
|
||||
@@ -77,6 +80,8 @@ namespace TweetDck.Core.Handling{
|
||||
AddDebugMenuItems(globalMenu);
|
||||
#endif
|
||||
}
|
||||
|
||||
RemoveSeparatorIfLast(model);
|
||||
}
|
||||
|
||||
public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){
|
||||
@@ -90,19 +95,19 @@ namespace TweetDck.Core.Handling{
|
||||
return true;
|
||||
|
||||
case MenuSettings:
|
||||
form.InvokeSafe(form.OpenSettings);
|
||||
form.InvokeAsyncSafe(form.OpenSettings);
|
||||
return true;
|
||||
|
||||
case MenuAbout:
|
||||
form.InvokeSafe(form.OpenAbout);
|
||||
form.InvokeAsyncSafe(form.OpenAbout);
|
||||
return true;
|
||||
|
||||
case MenuPlugins:
|
||||
form.InvokeSafe(form.OpenPlugins);
|
||||
form.InvokeAsyncSafe(form.OpenPlugins);
|
||||
return true;
|
||||
|
||||
case MenuMute:
|
||||
form.InvokeSafe(() => {
|
||||
form.InvokeAsyncSafe(() => {
|
||||
Program.UserConfig.MuteNotifications = !Program.UserConfig.MuteNotifications;
|
||||
Program.UserConfig.Save();
|
||||
});
|
||||
@@ -114,11 +119,11 @@ namespace TweetDck.Core.Handling{
|
||||
return true;
|
||||
|
||||
case MenuCopyTweetUrl:
|
||||
Clipboard.SetText(lastHighlightedTweet, TextDataFormat.UnicodeText);
|
||||
SetClipboardText(lastHighlightedTweet);
|
||||
return true;
|
||||
|
||||
case MenuScreenshotTweet:
|
||||
form.InvokeSafe(form.TriggerTweetScreenshot);
|
||||
form.InvokeAsyncSafe(form.TriggerTweetScreenshot);
|
||||
return true;
|
||||
|
||||
case MenuOpenQuotedTweetUrl:
|
||||
@@ -126,7 +131,7 @@ namespace TweetDck.Core.Handling{
|
||||
return true;
|
||||
|
||||
case MenuCopyQuotedTweetUrl:
|
||||
Clipboard.SetText(lastHighlightedQuotedTweet, TextDataFormat.UnicodeText);
|
||||
SetClipboardText(lastHighlightedQuotedTweet);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System.Windows.Forms;
|
||||
using CefSharp;
|
||||
using CefSharp;
|
||||
using TweetDck.Core.Controls;
|
||||
|
||||
namespace TweetDck.Core.Handling{
|
||||
@@ -12,13 +11,19 @@ namespace TweetDck.Core.Handling{
|
||||
private readonly FormNotification form;
|
||||
private readonly bool enableCustomMenu;
|
||||
|
||||
public ContextMenuNotification(FormNotification form, bool enableCustomMenu){
|
||||
public ContextMenuNotification(FormNotification form, bool enableCustomMenu) : base(form){
|
||||
this.form = form;
|
||||
this.enableCustomMenu = enableCustomMenu;
|
||||
}
|
||||
|
||||
public override void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, IMenuModel model){
|
||||
model.Clear();
|
||||
|
||||
if (parameters.TypeFlags.HasFlag(ContextMenuType.Selection)){
|
||||
model.AddItem(CefMenuCommand.Copy, "Copy");
|
||||
model.AddSeparator();
|
||||
}
|
||||
|
||||
base.OnBeforeContextMenu(browserControl, browser, frame, parameters, model);
|
||||
|
||||
if (enableCustomMenu){
|
||||
@@ -44,7 +49,7 @@ namespace TweetDck.Core.Handling{
|
||||
|
||||
RemoveSeparatorIfLast(model);
|
||||
|
||||
form.InvokeSafe(() => form.ContextMenuOpen = true);
|
||||
form.InvokeAsyncSafe(() => form.ContextMenuOpen = true);
|
||||
}
|
||||
|
||||
public override bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame, IContextMenuParams parameters, CefMenuCommand commandId, CefEventFlags eventFlags){
|
||||
@@ -54,19 +59,19 @@ namespace TweetDck.Core.Handling{
|
||||
|
||||
switch((int)commandId){
|
||||
case MenuSkipTweet:
|
||||
form.InvokeSafe(form.FinishCurrentTweet);
|
||||
form.InvokeAsyncSafe(form.FinishCurrentTweet);
|
||||
return true;
|
||||
|
||||
case MenuFreeze:
|
||||
form.InvokeSafe(() => form.FreezeTimer = !form.FreezeTimer);
|
||||
form.InvokeAsyncSafe(() => form.FreezeTimer = !form.FreezeTimer);
|
||||
return true;
|
||||
|
||||
case MenuCopyTweetUrl:
|
||||
Clipboard.SetText(form.CurrentUrl, TextDataFormat.UnicodeText);
|
||||
SetClipboardText(form.CurrentUrl);
|
||||
return true;
|
||||
|
||||
case MenuCopyQuotedTweetUrl:
|
||||
Clipboard.SetText(form.CurrentQuotedTweetUrl, TextDataFormat.UnicodeText);
|
||||
SetClipboardText(form.CurrentQuotedTweetUrl);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -75,7 +80,7 @@ namespace TweetDck.Core.Handling{
|
||||
|
||||
public override void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame){
|
||||
base.OnContextMenuDismissed(browserControl, browser, frame);
|
||||
form.InvokeSafe(() => form.ContextMenuOpen = false);
|
||||
form.InvokeAsyncSafe(() => form.ContextMenuOpen = false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -12,7 +12,7 @@ namespace TweetDck.Core.Notification.Screenshot{
|
||||
|
||||
browser.FrameLoadEnd += (sender, args) => {
|
||||
if (args.Frame.IsMain && browser.Address != "about:blank"){
|
||||
ScriptLoader.ExecuteScript(args.Frame, "window.setTimeout(() => $TD_NotificationScreenshot.trigger(), 25)", "gen:screenshot");
|
||||
ScriptLoader.ExecuteScript(args.Frame, "window.setTimeout($TD_NotificationScreenshot.trigger, 25)", "gen:screenshot");
|
||||
}
|
||||
};
|
||||
|
||||
|
66
Core/Notification/SoundNotification.cs
Normal file
66
Core/Notification/SoundNotification.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Media;
|
||||
using System.Windows.Forms;
|
||||
using TweetDck.Core.Other;
|
||||
|
||||
namespace TweetDck.Core.Notification{
|
||||
class SoundNotification : IDisposable{
|
||||
private readonly FormBrowser browserForm;
|
||||
|
||||
private SoundPlayer notificationSound;
|
||||
private bool ignoreNotificationSoundError;
|
||||
|
||||
public SoundNotification(FormBrowser browserForm){
|
||||
this.browserForm = browserForm;
|
||||
}
|
||||
|
||||
public void Play(string file){
|
||||
if (notificationSound == null){
|
||||
notificationSound = new SoundPlayer{
|
||||
LoadTimeout = 5000
|
||||
};
|
||||
}
|
||||
|
||||
if (notificationSound.SoundLocation != file){
|
||||
notificationSound.SoundLocation = file;
|
||||
ignoreNotificationSoundError = false;
|
||||
}
|
||||
|
||||
try{
|
||||
notificationSound.Play();
|
||||
}catch(FileNotFoundException e){
|
||||
OnNotificationSoundError("File not found: "+e.FileName);
|
||||
}catch(InvalidOperationException){
|
||||
OnNotificationSoundError("File is not a valid sound file.");
|
||||
}catch(TimeoutException){
|
||||
OnNotificationSoundError("File took too long to load.");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnNotificationSoundError(string message){
|
||||
if (!ignoreNotificationSoundError){
|
||||
ignoreNotificationSoundError = true;
|
||||
|
||||
using(FormMessage form = new FormMessage("Notification Sound Error", "Could not play custom notification sound."+Environment.NewLine+message, MessageBoxIcon.Error)){
|
||||
form.AddButton("Ignore");
|
||||
|
||||
Button btnOpenSettings = form.AddButton("Open Settings");
|
||||
btnOpenSettings.Width += 16;
|
||||
btnOpenSettings.Location = new Point(btnOpenSettings.Location.X-16, btnOpenSettings.Location.Y);
|
||||
|
||||
if (form.ShowDialog() == DialogResult.OK && form.ClickedButton == btnOpenSettings){
|
||||
browserForm.OpenSettings(FormSettings.TabIndexNotification);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose(){
|
||||
if (notificationSound != null){
|
||||
notificationSound.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -9,10 +9,13 @@ using TweetDck.Updates;
|
||||
|
||||
namespace TweetDck.Core.Other{
|
||||
sealed partial class FormSettings : Form{
|
||||
public const int TabIndexNotification = 1;
|
||||
|
||||
private readonly FormBrowser browser;
|
||||
private readonly Dictionary<Type, BaseTabSettings> tabs = new Dictionary<Type, BaseTabSettings>(4);
|
||||
private readonly bool hasFinishedLoading;
|
||||
|
||||
public FormSettings(FormBrowser browser, PluginManager plugins, UpdateHandler updates){
|
||||
public FormSettings(FormBrowser browser, PluginManager plugins, UpdateHandler updates, int startTabIndex = 0){
|
||||
InitializeComponent();
|
||||
|
||||
Text = Program.BrandName+" Settings";
|
||||
@@ -22,10 +25,12 @@ namespace TweetDck.Core.Other{
|
||||
|
||||
this.tabPanel.SetupTabPanel(100);
|
||||
this.tabPanel.AddButton("General", SelectTab<TabSettingsGeneral>);
|
||||
this.tabPanel.AddButton("Notifications", () => SelectTab(() => new TabSettingsNotifications(browser.CreateNotificationForm(NotificationFlags.DisableContextMenu))));
|
||||
this.tabPanel.AddButton("Notifications", () => SelectTab(() => new TabSettingsNotifications(browser.CreateNotificationForm(NotificationFlags.DisableContextMenu), !hasFinishedLoading)));
|
||||
this.tabPanel.AddButton("Updates", () => SelectTab(() => new TabSettingsUpdates(updates)));
|
||||
this.tabPanel.AddButton("Advanced", () => SelectTab(() => new TabSettingsAdvanced(browser.ReloadBrowser, plugins)));
|
||||
this.tabPanel.SelectTab(tabPanel.Buttons.First());
|
||||
this.tabPanel.AddButton("Advanced", () => SelectTab(() => new TabSettingsAdvanced(browser.ReinjectCustomCSS, plugins)));
|
||||
|
||||
this.tabPanel.SelectTab(tabPanel.Buttons.ElementAt(startTabIndex));
|
||||
hasFinishedLoading = true;
|
||||
}
|
||||
|
||||
private void SelectTab<T>() where T : BaseTabSettings, new(){
|
||||
|
@@ -23,6 +23,7 @@
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent() {
|
||||
this.components = new System.ComponentModel.Container();
|
||||
this.textBoxBrowserCSS = new System.Windows.Forms.TextBox();
|
||||
this.btnCancel = new System.Windows.Forms.Button();
|
||||
this.btnApply = new System.Windows.Forms.Button();
|
||||
@@ -32,6 +33,7 @@
|
||||
this.textBoxNotificationCSS = new System.Windows.Forms.TextBox();
|
||||
this.labelWarning = new System.Windows.Forms.Label();
|
||||
this.btnOpenWiki = new System.Windows.Forms.Button();
|
||||
this.timerTestBrowser = new System.Windows.Forms.Timer(this.components);
|
||||
((System.ComponentModel.ISupportInitialize)(this.splitContainer)).BeginInit();
|
||||
this.splitContainer.Panel1.SuspendLayout();
|
||||
this.splitContainer.Panel2.SuspendLayout();
|
||||
@@ -52,6 +54,7 @@
|
||||
this.textBoxBrowserCSS.Size = new System.Drawing.Size(373, 253);
|
||||
this.textBoxBrowserCSS.TabIndex = 0;
|
||||
this.textBoxBrowserCSS.WordWrap = false;
|
||||
this.textBoxBrowserCSS.KeyUp += new System.Windows.Forms.KeyEventHandler(this.textBoxBrowserCSS_KeyUp);
|
||||
//
|
||||
// btnCancel
|
||||
//
|
||||
@@ -132,7 +135,7 @@
|
||||
this.textBoxNotificationCSS.Multiline = true;
|
||||
this.textBoxNotificationCSS.Name = "textBoxNotificationCSS";
|
||||
this.textBoxNotificationCSS.ScrollBars = System.Windows.Forms.ScrollBars.Both;
|
||||
this.textBoxNotificationCSS.Size = new System.Drawing.Size(376, 253);
|
||||
this.textBoxNotificationCSS.Size = new System.Drawing.Size(373, 253);
|
||||
this.textBoxNotificationCSS.TabIndex = 1;
|
||||
this.textBoxNotificationCSS.WordWrap = false;
|
||||
//
|
||||
@@ -159,6 +162,11 @@
|
||||
this.btnOpenWiki.UseVisualStyleBackColor = true;
|
||||
this.btnOpenWiki.Click += new System.EventHandler(this.btnOpenWiki_Click);
|
||||
//
|
||||
// timerTestBrowser
|
||||
//
|
||||
this.timerTestBrowser.Interval = 500;
|
||||
this.timerTestBrowser.Tick += new System.EventHandler(this.timerTestBrowser_Tick);
|
||||
//
|
||||
// DialogSettingsCSS
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
@@ -195,5 +203,6 @@
|
||||
private System.Windows.Forms.Label labelNotification;
|
||||
private System.Windows.Forms.Label labelWarning;
|
||||
private System.Windows.Forms.Button btnOpenWiki;
|
||||
private System.Windows.Forms.Timer timerTestBrowser;
|
||||
}
|
||||
}
|
@@ -17,10 +17,14 @@ namespace TweetDck.Core.Other.Settings.Dialogs{
|
||||
}
|
||||
}
|
||||
|
||||
public DialogSettingsCSS(){
|
||||
private readonly Action<string> reinjectBrowserCSS;
|
||||
|
||||
public DialogSettingsCSS(Action<string> reinjectBrowserCSS){
|
||||
InitializeComponent();
|
||||
|
||||
Text = Program.BrandName+" Settings - CSS";
|
||||
|
||||
this.reinjectBrowserCSS = reinjectBrowserCSS;
|
||||
|
||||
textBoxBrowserCSS.EnableMultilineShortcuts();
|
||||
textBoxBrowserCSS.Text = Program.UserConfig.CustomBrowserCSS ?? "";
|
||||
@@ -29,6 +33,16 @@ namespace TweetDck.Core.Other.Settings.Dialogs{
|
||||
textBoxNotificationCSS.Text = Program.UserConfig.CustomNotificationCSS ?? "";
|
||||
}
|
||||
|
||||
private void textBoxBrowserCSS_KeyUp(object sender, KeyEventArgs e){
|
||||
timerTestBrowser.Stop();
|
||||
timerTestBrowser.Start();
|
||||
}
|
||||
|
||||
private void timerTestBrowser_Tick(object sender, EventArgs e){
|
||||
reinjectBrowserCSS(textBoxBrowserCSS.Text);
|
||||
timerTestBrowser.Stop();
|
||||
}
|
||||
|
||||
private void btnOpenWiki_Click(object sender, EventArgs e){
|
||||
BrowserUtils.OpenExternalBrowser("https://github.com/chylex/TweetDuck/wiki");
|
||||
}
|
||||
|
@@ -104,6 +104,7 @@
|
||||
this.Controls.Add(this.cbConfig);
|
||||
this.Controls.Add(this.btnApply);
|
||||
this.Controls.Add(this.btnCancel);
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
|
||||
this.MinimumSize = new System.Drawing.Size(200, 170);
|
||||
this.Name = "DialogSettingsExport";
|
||||
this.ShowIcon = false;
|
||||
|
@@ -121,8 +121,11 @@ namespace TweetDck.Core.Other.Settings.Export{
|
||||
|
||||
public void WriteToFile(string path, bool createDirectory){
|
||||
if (createDirectory){
|
||||
// ReSharper disable once AssignNullToNotNullAttribute
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
||||
string dir = Path.GetDirectoryName(path);
|
||||
|
||||
if (!string.IsNullOrEmpty(dir)){
|
||||
Directory.CreateDirectory(dir);
|
||||
}
|
||||
}
|
||||
|
||||
File.WriteAllBytes(path, contents);
|
||||
|
@@ -9,13 +9,13 @@ using TweetDck.Plugins;
|
||||
|
||||
namespace TweetDck.Core.Other.Settings{
|
||||
partial class TabSettingsAdvanced : BaseTabSettings{
|
||||
private readonly Action browserReloadAction;
|
||||
private readonly Action<string> reinjectBrowserCSS;
|
||||
private readonly PluginManager plugins;
|
||||
|
||||
public TabSettingsAdvanced(Action browserReloadAction, PluginManager plugins){
|
||||
public TabSettingsAdvanced(Action<string> reinjectBrowserCSS, PluginManager plugins){
|
||||
InitializeComponent();
|
||||
|
||||
this.browserReloadAction = browserReloadAction;
|
||||
this.reinjectBrowserCSS = reinjectBrowserCSS;
|
||||
this.plugins = plugins;
|
||||
|
||||
checkHardwareAcceleration.Checked = HardwareAcceleration.IsEnabled;
|
||||
@@ -80,17 +80,13 @@ namespace TweetDck.Core.Other.Settings{
|
||||
}
|
||||
|
||||
private void btnEditCSS_Click(object sender, EventArgs e){
|
||||
using(DialogSettingsCSS form = new DialogSettingsCSS()){
|
||||
using(DialogSettingsCSS form = new DialogSettingsCSS(reinjectBrowserCSS)){
|
||||
if (form.ShowDialog(ParentForm) == DialogResult.OK){
|
||||
bool hasChangedBrowser = form.BrowserCSS != Config.CustomBrowserCSS;
|
||||
|
||||
Config.CustomBrowserCSS = form.BrowserCSS;
|
||||
Config.CustomNotificationCSS = form.NotificationCSS;
|
||||
|
||||
if (hasChangedBrowser && MessageBox.Show("The browser CSS has changed, do you want to reload it?", "Browser CSS Changed", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2) == DialogResult.Yes){
|
||||
browserReloadAction();
|
||||
}
|
||||
}
|
||||
|
||||
reinjectBrowserCSS(Config.CustomBrowserCSS); // reinject on cancel too, because the CSS is updated while typing
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -381,6 +381,7 @@
|
||||
this.tbCustomSound.Name = "tbCustomSound";
|
||||
this.tbCustomSound.Size = new System.Drawing.Size(170, 20);
|
||||
this.tbCustomSound.TabIndex = 0;
|
||||
this.tbCustomSound.TextChanged += new System.EventHandler(this.tbCustomSound_TextChanged);
|
||||
//
|
||||
// TabSettingsNotifications
|
||||
//
|
||||
|
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Windows.Forms;
|
||||
using TweetDck.Core.Controls;
|
||||
using TweetDck.Core.Notification;
|
||||
@@ -11,7 +12,7 @@ namespace TweetDck.Core.Other.Settings{
|
||||
private readonly FormNotification notification;
|
||||
private readonly Point initCursorPosition;
|
||||
|
||||
public TabSettingsNotifications(FormNotification notification){
|
||||
public TabSettingsNotifications(FormNotification notification, bool ignoreAutoClick){
|
||||
InitializeComponent();
|
||||
|
||||
this.notification = notification;
|
||||
@@ -30,7 +31,7 @@ namespace TweetDck.Core.Other.Settings{
|
||||
this.notification.Activated += notification_Activated;
|
||||
this.notification.Show(this);
|
||||
|
||||
initCursorPosition = Cursor.Position;
|
||||
initCursorPosition = ignoreAutoClick ? ControlExtensions.InvisibleLocation : Cursor.Position;
|
||||
|
||||
switch(Config.NotificationPosition){
|
||||
case TweetNotification.Position.TopLeft: radioLocTL.Checked = true; break;
|
||||
@@ -58,7 +59,7 @@ namespace TweetDck.Core.Other.Settings{
|
||||
trackBarEdgeDistance.SetValueSafe(Config.NotificationEdgeDistance);
|
||||
labelEdgeDistanceValue.Text = trackBarEdgeDistance.Value.ToString(CultureInfo.InvariantCulture)+" px";
|
||||
|
||||
tbCustomSound.Text = Config.NotificationSoundPath ?? string.Empty;
|
||||
tbCustomSound.Text = Config.NotificationSoundPath;
|
||||
|
||||
Disposed += (sender, args) => this.notification.Dispose();
|
||||
}
|
||||
@@ -77,7 +78,7 @@ namespace TweetDck.Core.Other.Settings{
|
||||
}
|
||||
|
||||
private void notification_Activated(object sender, EventArgs e){
|
||||
if (Cursor.Position == initCursorPosition){
|
||||
if (Cursor.Position == initCursorPosition && initCursorPosition != ControlExtensions.InvisibleLocation){
|
||||
Timer delay = WindowsUtils.CreateSingleTickTimer(1);
|
||||
|
||||
delay.Tick += (sender2, args2) => { // here you can see a disgusting hack to force the freshly opened notification window out of focus
|
||||
@@ -167,6 +168,13 @@ namespace TweetDck.Core.Other.Settings{
|
||||
notification.ShowNotificationForSettings(false);
|
||||
}
|
||||
|
||||
private void tbCustomSound_TextChanged(object sender, EventArgs e){
|
||||
// also runs when the control is created, i.e. when Ready is false
|
||||
|
||||
bool fileExists = string.IsNullOrEmpty(tbCustomSound.Text) || File.Exists(tbCustomSound.Text);
|
||||
tbCustomSound.ForeColor = fileExists ? SystemColors.WindowText : Color.Maroon;
|
||||
}
|
||||
|
||||
private void btnBrowseSound_Click(object sender, EventArgs e){
|
||||
using(OpenFileDialog dialog = new OpenFileDialog{
|
||||
AutoUpgradeEnabled = true,
|
||||
|
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using TweetDck.Updates;
|
||||
using TweetDck.Updates.Events;
|
||||
|
||||
namespace TweetDck.Core.Other.Settings{
|
||||
partial class TabSettingsUpdates : BaseTabSettings{
|
||||
@@ -27,12 +28,18 @@ namespace TweetDck.Core.Other.Settings{
|
||||
private void btnCheckUpdates_Click(object sender, EventArgs e){
|
||||
if (!Ready)return;
|
||||
|
||||
Config.DismissedUpdate = string.Empty;
|
||||
Config.Save();
|
||||
|
||||
updateCheckEventId = updates.Check(true);
|
||||
|
||||
btnCheckUpdates.Enabled = false;
|
||||
if (updateCheckEventId == -1){
|
||||
MessageBox.Show("Sorry, your system is no longer supported.", "Unsupported System", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
}
|
||||
else{
|
||||
btnCheckUpdates.Enabled = false;
|
||||
|
||||
updates.Settings.DismissedUpdate = string.Empty;
|
||||
Config.DismissedUpdate = string.Empty;
|
||||
Config.Save();
|
||||
}
|
||||
}
|
||||
|
||||
private void updates_CheckFinished(object sender, UpdateCheckEventArgs e){
|
||||
|
@@ -105,7 +105,7 @@ namespace TweetDck.Core.Utils{
|
||||
build.Append(kvp.Key).Append(" \"").Append(kvp.Value).Append("\" ");
|
||||
}
|
||||
|
||||
return build.Remove(build.Length-1, 1).ToString();
|
||||
return build.Length == 0 ? string.Empty : build.Remove(build.Length-1, 1).ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -24,13 +24,26 @@ namespace TweetDck.Core.Utils{
|
||||
Left, Right
|
||||
}
|
||||
|
||||
private struct LASTINPUTINFO{
|
||||
public static readonly uint Size = (uint)Marshal.SizeOf(typeof(LASTINPUTINFO));
|
||||
|
||||
// ReSharper disable once NotAccessedField.Local
|
||||
public uint cbSize;
|
||||
#pragma warning disable 649
|
||||
public uint dwTime;
|
||||
#pragma warning restore 649
|
||||
}
|
||||
|
||||
public delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam);
|
||||
|
||||
[DllImport("user32.dll", EntryPoint = "SetWindowPos")]
|
||||
public static extern bool SetWindowPos(int hWnd, int hWndOrder, int x, int y, int width, int height, uint flags);
|
||||
private static extern bool SetWindowPos(int hWnd, int hWndOrder, int x, int y, int width, int height, uint flags);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern void mouse_event(int dwFlags, int dx, int dy, int cButtons, int dwExtraInfo);
|
||||
private static extern void mouse_event(int dwFlags, int dx, int dy, int cButtons, int dwExtraInfo);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool GetLastInputInfo(ref LASTINPUTINFO info);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
public static extern IntPtr SendMessage(IntPtr hWnd, uint msg, int wParam, IntPtr lParam);
|
||||
@@ -75,5 +88,23 @@ namespace TweetDck.Core.Utils{
|
||||
mouse_event(flagHold, Cursor.Position.X, Cursor.Position.Y, 0, 0);
|
||||
mouse_event(flagRelease, Cursor.Position.X, Cursor.Position.Y, 0, 0);
|
||||
}
|
||||
|
||||
public static int GetIdleSeconds(){
|
||||
LASTINPUTINFO info = new LASTINPUTINFO();
|
||||
info.cbSize = LASTINPUTINFO.Size;
|
||||
|
||||
if (!GetLastInputInfo(ref info)){
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint ticks;
|
||||
|
||||
unchecked{
|
||||
ticks = (uint)Environment.TickCount;
|
||||
}
|
||||
|
||||
int seconds = (int)Math.Floor(TimeSpan.FromMilliseconds(ticks-info.dwTime).TotalSeconds);
|
||||
return Math.Max(0, seconds); // ignore rollover after several weeks of uptime
|
||||
}
|
||||
}
|
||||
}
|
||||
|
100
Core/Utils/TwoKeyDictionary.cs
Normal file
100
Core/Utils/TwoKeyDictionary.cs
Normal file
@@ -0,0 +1,100 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace TweetDck.Core.Utils{
|
||||
class TwoKeyDictionary<K1, K2, V>{
|
||||
private readonly Dictionary<K1, Dictionary<K2, V>> dict;
|
||||
private readonly int innerCapacity;
|
||||
|
||||
public TwoKeyDictionary() : this(16, 16){}
|
||||
|
||||
public TwoKeyDictionary(int outerCapacity, int innerCapacity){
|
||||
this.dict = new Dictionary<K1, Dictionary<K2, V>>(outerCapacity);
|
||||
this.innerCapacity = innerCapacity;
|
||||
}
|
||||
|
||||
// Properties
|
||||
|
||||
public V this[K1 outerKey, K2 innerKey]{
|
||||
get{ // throws on missing key
|
||||
return dict[outerKey][innerKey];
|
||||
}
|
||||
|
||||
set{
|
||||
Dictionary<K2, V> innerDict;
|
||||
|
||||
if (!dict.TryGetValue(outerKey, out innerDict)){
|
||||
dict.Add(outerKey, innerDict = new Dictionary<K2, V>(innerCapacity));
|
||||
}
|
||||
|
||||
innerDict[innerKey] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Members
|
||||
|
||||
public void Add(K1 outerKey, K2 innerKey, V value){ // throws on duplicate
|
||||
Dictionary<K2, V> innerDict;
|
||||
|
||||
if (!dict.TryGetValue(outerKey, out innerDict)){
|
||||
dict.Add(outerKey, innerDict = new Dictionary<K2, V>(innerCapacity));
|
||||
}
|
||||
|
||||
innerDict.Add(innerKey, value);
|
||||
}
|
||||
|
||||
public void Clear(){
|
||||
this.dict.Clear();
|
||||
}
|
||||
|
||||
public void Clear(K1 outerKey){ // throws on missing key, but keeps the key unlike Remove(K1)
|
||||
dict[outerKey].Clear();
|
||||
}
|
||||
|
||||
public bool Contains(K1 outerKey){
|
||||
return dict.ContainsKey(outerKey);
|
||||
}
|
||||
|
||||
public bool Contains(K1 outerKey, K2 innerKey){
|
||||
Dictionary<K2, V> innerDict;
|
||||
return dict.TryGetValue(outerKey, out innerDict) && innerDict.ContainsKey(innerKey);
|
||||
}
|
||||
|
||||
public int Count(){
|
||||
return dict.Values.Sum(d => d.Count);
|
||||
}
|
||||
|
||||
public int Count(K1 outerKey){ // throws on missing key
|
||||
return dict[outerKey].Count;
|
||||
}
|
||||
|
||||
public bool Remove(K1 outerKey){
|
||||
return dict.Remove(outerKey);
|
||||
}
|
||||
|
||||
public bool Remove(K1 outerKey, K2 innerKey){
|
||||
Dictionary<K2, V> innerDict;
|
||||
|
||||
if (dict.TryGetValue(outerKey, out innerDict) && innerDict.Remove(innerKey)){
|
||||
if (innerDict.Count == 0){
|
||||
dict.Remove(outerKey);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else return false;
|
||||
}
|
||||
|
||||
public bool TryGetValue(K1 outerKey, K2 innerKey, out V value){
|
||||
Dictionary<K2, V> innerDict;
|
||||
|
||||
if (dict.TryGetValue(outerKey, out innerDict)){
|
||||
return innerDict.TryGetValue(innerKey, out value);
|
||||
}
|
||||
else{
|
||||
value = default(V);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,7 +1,11 @@
|
||||
using System.Diagnostics;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Windows.Forms;
|
||||
using Timer = System.Windows.Forms.Timer;
|
||||
|
||||
namespace TweetDck.Core.Utils{
|
||||
static class WindowsUtils{
|
||||
@@ -44,18 +48,53 @@ namespace TweetDck.Core.Utils{
|
||||
return timer;
|
||||
}
|
||||
|
||||
public static bool TrySleepUntil(Func<bool> test, int timeoutMillis, int timeStepMillis){
|
||||
for(int waited = 0; waited < timeoutMillis; waited += timeStepMillis){
|
||||
if (test()){
|
||||
return true;
|
||||
}
|
||||
|
||||
Thread.Sleep(timeStepMillis);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void ClipboardStripHtmlStyles(){
|
||||
if (!Clipboard.ContainsText(TextDataFormat.Html)){
|
||||
return;
|
||||
}
|
||||
|
||||
string original = Clipboard.GetText(TextDataFormat.Html);
|
||||
string updated = RegexStripHtmlStyles.Replace(original, string.Empty);
|
||||
string originalText = Clipboard.GetText(TextDataFormat.UnicodeText);
|
||||
string originalHtml = Clipboard.GetText(TextDataFormat.Html);
|
||||
|
||||
int removed = original.Length-updated.Length;
|
||||
updated = RegexOffsetClipboardHtml.Replace(updated, match => (int.Parse(match.Value)-removed).ToString().PadLeft(match.Value.Length, '0'));
|
||||
string updatedHtml = RegexStripHtmlStyles.Replace(originalHtml, string.Empty);
|
||||
|
||||
Clipboard.SetText(updated, TextDataFormat.Html);
|
||||
int removed = originalHtml.Length-updatedHtml.Length;
|
||||
updatedHtml = RegexOffsetClipboardHtml.Replace(updatedHtml, match => (int.Parse(match.Value)-removed).ToString().PadLeft(match.Value.Length, '0'));
|
||||
|
||||
DataObject obj = new DataObject();
|
||||
obj.SetText(originalText, TextDataFormat.UnicodeText);
|
||||
obj.SetText(updatedHtml, TextDataFormat.Html);
|
||||
SetClipboardData(obj);
|
||||
}
|
||||
|
||||
public static void SetClipboard(string text, TextDataFormat format){
|
||||
if (string.IsNullOrEmpty(text)){
|
||||
return;
|
||||
}
|
||||
|
||||
DataObject obj = new DataObject();
|
||||
obj.SetText(text, format);
|
||||
SetClipboardData(obj);
|
||||
}
|
||||
|
||||
private static void SetClipboardData(DataObject obj){
|
||||
try{
|
||||
Clipboard.SetDataObject(obj);
|
||||
}catch(ExternalException e){
|
||||
Program.Reporter.HandleException("Clipboard Error", Program.BrandName+" could not access the clipboard as it is currently used by another process.", true, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,24 +1,35 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using TweetDck.Core.Utils;
|
||||
using TweetDck.Plugins.Enums;
|
||||
using TweetDck.Plugins.Events;
|
||||
|
||||
namespace TweetDck.Plugins{
|
||||
class PluginBridge{
|
||||
private static string SanitizeCacheKey(string key){
|
||||
return key.Replace('\\', '/').Trim();
|
||||
}
|
||||
|
||||
private readonly PluginManager manager;
|
||||
private readonly Dictionary<string, string> fileCache = new Dictionary<string, string>(2);
|
||||
private readonly TwoKeyDictionary<int, string, string> fileCache = new TwoKeyDictionary<int, string, string>(4, 2);
|
||||
|
||||
public PluginBridge(PluginManager manager){
|
||||
this.manager = manager;
|
||||
this.manager.Reloaded += manager_Reloaded;
|
||||
this.manager.PluginChangedState += manager_PluginChangedState;
|
||||
}
|
||||
|
||||
private void manager_Reloaded(object sender, PluginLoadEventArgs e){
|
||||
fileCache.Clear();
|
||||
}
|
||||
|
||||
private void manager_PluginChangedState(object sender, PluginChangedStateEventArgs e){
|
||||
if (!e.IsEnabled){
|
||||
fileCache.Remove(manager.GetTokenFromPlugin(e.Plugin));
|
||||
}
|
||||
}
|
||||
|
||||
private string GetFullPathOrThrow(int token, PluginFolder folder, string path){
|
||||
Plugin plugin = manager.GetPluginFromToken(token);
|
||||
string fullPath = plugin == null ? string.Empty : plugin.GetFullPathIfSafe(folder, path);
|
||||
@@ -35,15 +46,17 @@ namespace TweetDck.Plugins{
|
||||
}
|
||||
}
|
||||
|
||||
private string ReadFileUnsafe(string fullPath, bool readCached){
|
||||
private string ReadFileUnsafe(int token, string cacheKey, string fullPath, bool readCached){
|
||||
cacheKey = SanitizeCacheKey(cacheKey);
|
||||
|
||||
string cachedContents;
|
||||
|
||||
if (readCached && fileCache.TryGetValue(fullPath, out cachedContents)){
|
||||
if (readCached && fileCache.TryGetValue(token, cacheKey, out cachedContents)){
|
||||
return cachedContents;
|
||||
}
|
||||
|
||||
try{
|
||||
return fileCache[fullPath] = File.ReadAllText(fullPath, Encoding.UTF8);
|
||||
return fileCache[token, cacheKey] = File.ReadAllText(fullPath, Encoding.UTF8);
|
||||
}catch(FileNotFoundException){
|
||||
throw new Exception("File not found.");
|
||||
}catch(DirectoryNotFoundException){
|
||||
@@ -60,17 +73,17 @@ namespace TweetDck.Plugins{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(fullPath));
|
||||
|
||||
File.WriteAllText(fullPath, contents, Encoding.UTF8);
|
||||
fileCache[fullPath] = contents;
|
||||
fileCache[token, SanitizeCacheKey(path)] = contents;
|
||||
}
|
||||
|
||||
public string ReadFile(int token, string path, bool cache){
|
||||
return ReadFileUnsafe(GetFullPathOrThrow(token, PluginFolder.Data, path), cache);
|
||||
return ReadFileUnsafe(token, path, GetFullPathOrThrow(token, PluginFolder.Data, path), cache);
|
||||
}
|
||||
|
||||
public void DeleteFile(int token, string path){
|
||||
string fullPath = GetFullPathOrThrow(token, PluginFolder.Data, path);
|
||||
|
||||
fileCache.Remove(fullPath);
|
||||
fileCache.Remove(token, SanitizeCacheKey(path));
|
||||
File.Delete(fullPath);
|
||||
}
|
||||
|
||||
@@ -79,7 +92,7 @@ namespace TweetDck.Plugins{
|
||||
}
|
||||
|
||||
public string ReadFileRoot(int token, string path){
|
||||
return ReadFileUnsafe(GetFullPathOrThrow(token, PluginFolder.Root, path), true);
|
||||
return ReadFileUnsafe(token, "root*"+path, GetFullPathOrThrow(token, PluginFolder.Root, path), true);
|
||||
}
|
||||
|
||||
public bool CheckFileExistsRoot(int token, string path){
|
||||
|
@@ -13,6 +13,8 @@ namespace TweetDck.Plugins{
|
||||
public const string PluginNotificationScriptFile = "plugins.notification.js";
|
||||
public const string PluginGlobalScriptFile = "plugins.js";
|
||||
|
||||
private const int InvalidToken = 0;
|
||||
|
||||
public string PathOfficialPlugins { get { return Path.Combine(rootPath, "official"); } }
|
||||
public string PathCustomPlugins { get { return Path.Combine(rootPath, "user"); } }
|
||||
|
||||
@@ -63,6 +65,16 @@ namespace TweetDck.Plugins{
|
||||
return plugins.Any(plugin => plugin.Environments.HasFlag(environment));
|
||||
}
|
||||
|
||||
public int GetTokenFromPlugin(Plugin plugin){
|
||||
foreach(KeyValuePair<int, Plugin> kvp in tokens){
|
||||
if (kvp.Value.Equals(plugin)){
|
||||
return kvp.Key;
|
||||
}
|
||||
}
|
||||
|
||||
return InvalidToken;
|
||||
}
|
||||
|
||||
public Plugin GetPluginFromToken(int token){
|
||||
Plugin plugin;
|
||||
return tokens.TryGetValue(token, out plugin) ? plugin : null;
|
||||
@@ -108,7 +120,7 @@ namespace TweetDck.Plugins{
|
||||
int token;
|
||||
|
||||
if (tokens.ContainsValue(plugin)){
|
||||
token = tokens.First(kvp => kvp.Value.Equals(plugin)).Key;
|
||||
token = GetTokenFromPlugin(plugin);
|
||||
}
|
||||
else{
|
||||
token = GenerateToken();
|
||||
@@ -141,7 +153,7 @@ namespace TweetDck.Plugins{
|
||||
for(int attempt = 0; attempt < 1000; attempt++){
|
||||
int token = rand.Next();
|
||||
|
||||
if (!tokens.ContainsKey(token)){
|
||||
if (!tokens.ContainsKey(token) && token != InvalidToken){
|
||||
return token;
|
||||
}
|
||||
}
|
||||
|
33
Program.cs
33
Program.cs
@@ -21,8 +21,8 @@ namespace TweetDck{
|
||||
public const string BrandName = "TweetDuck";
|
||||
public const string Website = "https://tweetduck.chylex.com";
|
||||
|
||||
public const string VersionTag = "1.6.1";
|
||||
public const string VersionFull = "1.6.1.0";
|
||||
public const string VersionTag = "1.6.5";
|
||||
public const string VersionFull = "1.6.5.0";
|
||||
|
||||
public static readonly Version Version = new Version(VersionTag);
|
||||
|
||||
@@ -89,21 +89,26 @@ namespace TweetDck{
|
||||
return;
|
||||
}
|
||||
}
|
||||
else{
|
||||
Thread.Sleep(500);
|
||||
}
|
||||
else Thread.Sleep(500);
|
||||
}
|
||||
}
|
||||
else{
|
||||
LockManager.Result lockResult = LockManager.Lock();
|
||||
|
||||
if (lockResult == LockManager.Result.HasProcess){
|
||||
if (LockManager.LockingProcess.MainWindowHandle == IntPtr.Zero && LockManager.LockingProcess.Responding){ // restore if the original process is in tray
|
||||
NativeMethods.SendMessage(NativeMethods.HWND_BROADCAST, WindowRestoreMessage, 0, IntPtr.Zero);
|
||||
return;
|
||||
if (LockManager.LockingProcess.MainWindowHandle == IntPtr.Zero){ // restore if the original process is in tray
|
||||
NativeMethods.SendMessage(NativeMethods.HWND_BROADCAST, WindowRestoreMessage, LockManager.LockingProcess.Id, IntPtr.Zero);
|
||||
|
||||
if (WindowsUtils.TrySleepUntil(() => {
|
||||
LockManager.LockingProcess.Refresh();
|
||||
return LockManager.LockingProcess.HasExited || (LockManager.LockingProcess.MainWindowHandle != IntPtr.Zero && LockManager.LockingProcess.Responding);
|
||||
}, 2000, 250)){
|
||||
return; // should trigger on first attempt if succeeded, but wait just in case
|
||||
}
|
||||
}
|
||||
else if (MessageBox.Show("Another instance of "+BrandName+" is already running.\r\nDo you want to close it?", BrandName+" is Already Running", MessageBoxButtons.YesNo, MessageBoxIcon.Error, MessageBoxDefaultButton.Button2) == DialogResult.Yes){
|
||||
if (!LockManager.CloseLockingProcess(30000)){
|
||||
|
||||
if (MessageBox.Show("Another instance of "+BrandName+" is already running.\r\nDo you want to close it?", BrandName+" is Already Running", MessageBoxButtons.YesNo, MessageBoxIcon.Error, MessageBoxDefaultButton.Button2) == DialogResult.Yes){
|
||||
if (!LockManager.CloseLockingProcess(10000, 5000)){
|
||||
MessageBox.Show("Could not close the other process.", BrandName+" Has Failed :(", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
return;
|
||||
}
|
||||
@@ -125,6 +130,8 @@ namespace TweetDck{
|
||||
}
|
||||
|
||||
BrowserCache.ClearOldCacheFiles();
|
||||
|
||||
CefSharpSettings.WcfEnabled = false;
|
||||
|
||||
CefSettings settings = new CefSettings{
|
||||
AcceptLanguageList = BrowserUtils.HeaderAcceptLanguage,
|
||||
@@ -155,7 +162,8 @@ namespace TweetDck{
|
||||
plugins.Reload();
|
||||
|
||||
FormBrowser mainForm = new FormBrowser(plugins, new UpdaterSettings{
|
||||
AllowPreReleases = Args.HasFlag("-debugupdates")
|
||||
AllowPreReleases = Args.HasFlag("-debugupdates"),
|
||||
DismissedUpdate = UserConfig.DismissedUpdate
|
||||
});
|
||||
|
||||
Application.Run(mainForm);
|
||||
@@ -163,10 +171,11 @@ namespace TweetDck{
|
||||
if (mainForm.UpdateInstallerPath != null){
|
||||
ExitCleanup();
|
||||
|
||||
// ProgramPath has a trailing backslash
|
||||
string updaterArgs = "/SP- /SILENT /CLOSEAPPLICATIONS /UPDATEPATH=\""+ProgramPath+"\" /RUNARGS=\""+GetArgsClean().ToString().Replace("\"", "^\"")+"\""+(IsPortable ? " /PORTABLE=1" : "");
|
||||
bool runElevated = !IsPortable || !WindowsUtils.CheckFolderWritePermission(ProgramPath);
|
||||
|
||||
WindowsUtils.StartProcess(mainForm.UpdateInstallerPath, updaterArgs, runElevated); // ProgramPath has a trailing backslash
|
||||
WindowsUtils.StartProcess(mainForm.UpdateInstallerPath, updaterArgs, runElevated);
|
||||
Application.Exit();
|
||||
}
|
||||
}
|
||||
|
@@ -36,4 +36,8 @@ using TweetDck;
|
||||
[assembly: AssemblyVersion(Program.VersionFull)]
|
||||
[assembly: AssemblyFileVersion(Program.VersionFull)]
|
||||
|
||||
[assembly: NeutralResourcesLanguageAttribute("en")]
|
||||
[assembly: NeutralResourcesLanguageAttribute("en")]
|
||||
|
||||
#if DEBUG
|
||||
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("UnitTests")]
|
||||
#endif
|
||||
|
@@ -1,11 +1,11 @@
|
||||
# Build Instructions
|
||||
|
||||
The program was build using Visual Studio 2013. After opening the solution, make sure you have **CefSharp.WinForms** and **Microsoft.VC120.CRT.JetBrains** included - if not, download them using NuGet. For **CefSharp**, you will need version 53 or newer currently available as a pre-release.
|
||||
The program was build using Visual Studio 2013. After opening the solution, make sure you have **CefSharp.WinForms** and **Microsoft.VC120.CRT.JetBrains** included - if not, download them using NuGet.
|
||||
```
|
||||
PM> Install-Package CefSharp.WinForms -Version 53.0.1
|
||||
PM> Install-Package CefSharp.WinForms -Version 55.0.0
|
||||
PM> Install-Package Microsoft.VC120.CRT.JetBrains
|
||||
```
|
||||
|
||||
After building, run **_postbuild.bat** which deletes unnecessary files that CefSharp adds after post-build events >_>
|
||||
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. Do not run both files consecutively, otherwise the program will crash - if you want to do both, rebuild the project before running each file.
|
||||
|
||||
Built files are then available in **bin/x86** and/or **bin/x64**.
|
||||
Built files are then available in **bin/x86** and/or **bin/x64**, 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.
|
||||
|
14
Resources/Plugins/.debug/.meta
Normal file
14
Resources/Plugins/.debug/.meta
Normal file
@@ -0,0 +1,14 @@
|
||||
[name]
|
||||
Debug plugin
|
||||
|
||||
[description]
|
||||
- Runs several tests on startup, only included in debug configuration
|
||||
|
||||
[author]
|
||||
chylex
|
||||
|
||||
[version]
|
||||
1.0
|
||||
|
||||
[website]
|
||||
https://tweetduck.chylex.com
|
3
Resources/Plugins/.debug/browser.js
Normal file
3
Resources/Plugins/.debug/browser.js
Normal file
@@ -0,0 +1,3 @@
|
||||
enabled(){
|
||||
|
||||
}
|
@@ -2,6 +2,7 @@
|
||||
Revert TweetDeck design changes
|
||||
|
||||
[description]
|
||||
- Individually configurable options to revert and tweak TweetDeck design changes
|
||||
- Moves action menu to the right and hides it by default
|
||||
- Reverts interactive texts around tweets (such as 'Details' or 'Conversation')
|
||||
|
||||
@@ -9,10 +10,16 @@ Revert TweetDeck design changes
|
||||
chylex
|
||||
|
||||
[version]
|
||||
1.1
|
||||
1.2
|
||||
|
||||
[website]
|
||||
https://tweetduck.chylex.com
|
||||
|
||||
[configfile]
|
||||
configuration.js
|
||||
|
||||
[configdefault]
|
||||
configuration.default.js
|
||||
|
||||
[requires]
|
||||
1.4.1
|
@@ -1,34 +1,46 @@
|
||||
enabled(){
|
||||
// add a stylesheet to change tweet actions
|
||||
this.css = window.TDPF_createCustomStyle(this);
|
||||
this.css.insert(".tweet-actions { float: right !important; width: auto !important; }");
|
||||
this.css.insert(".tweet-action { opacity: 0; }");
|
||||
this.css.insert(".is-favorite .tweet-action, .is-retweet .tweet-action { opacity: 0.5; visibility: visible !important; }");
|
||||
this.css.insert(".tweet:hover .tweet-action, .is-favorite .tweet-action[rel='favorite'], .is-retweet .tweet-action[rel='retweet'] { opacity: 1; visibility: visible !important; }");
|
||||
this.css.insert(".tweet-actions > li:nth-child(4) { margin-right: 2px !important; }");
|
||||
|
||||
// revert small links around the tweet
|
||||
this.prevFooterMustache = TD.mustaches["status/tweet_single_footer.mustache"];
|
||||
|
||||
var footerLayout = TD.mustaches["status/tweet_single_footer.mustache"];
|
||||
footerLayout = footerLayout.replace('txt-mute txt-size--12', 'txt-mute txt-small');
|
||||
footerLayout = footerLayout.replace('{{#inReplyToID}}', '{{^inReplyToID}} <a class="pull-left margin-txs txt-mute txt-small is-vishidden-narrow" href="#" rel="viewDetails">{{_i}}Details{{/i}}</a> <a class="pull-left margin-txs txt-mute txt-small is-vishidden is-visshown-narrow" href="#" rel="viewDetails">{{_i}}Open{{/i}}</a> {{/inReplyToID}} {{#inReplyToID}}');
|
||||
footerLayout = footerLayout.replace('<span class="link-complex-target"> {{_i}}View Conversation{{/i}}', '<i class="icon icon-conversation icon-small-context"></i> <span class="link-complex-target"> <span class="is-vishidden-wide is-vishidden-narrow">{{_i}}View{{/i}}</span> <span class="is-vishidden is-visshown-wide">{{_i}}Conversation{{/i}}</span>');
|
||||
TD.mustaches["status/tweet_single_footer.mustache"] = footerLayout;
|
||||
|
||||
|
||||
// load configuration
|
||||
window.TDPF_loadConfigurationFile(this, "configuration.js", "configuration.default.js", config => {
|
||||
if (config.hideTweetActions){
|
||||
this.css.insert(".tweet-action { opacity: 0; }");
|
||||
this.css.insert(".is-favorite .tweet-action, .is-retweet .tweet-action { opacity: 0.5; visibility: visible !important; }");
|
||||
this.css.insert(".tweet:hover .tweet-action, .is-favorite .tweet-action[rel='favorite'], .is-retweet .tweet-action[rel='retweet'] { opacity: 1; visibility: visible !important; }");
|
||||
}
|
||||
|
||||
if (config.moveTweetActionsToRight){
|
||||
this.css.insert(".tweet-actions { float: right !important; width: auto !important; }");
|
||||
this.css.insert(".tweet-actions > li:nth-child(4) { margin-right: 2px !important; }");
|
||||
}
|
||||
|
||||
if (config.smallComposeTextSize){
|
||||
this.css.insert(".compose-text { font-size: 12px !important; height: 120px !important; }");
|
||||
}
|
||||
|
||||
if (config.revertConversationLinks){
|
||||
var footer = TD.mustaches["status/tweet_single_footer.mustache"];
|
||||
footer = footer.replace('txt-mute txt-size--12', 'txt-mute txt-small');
|
||||
footer = footer.replace('{{#inReplyToID}}', '{{^inReplyToID}} <a class="pull-left margin-txs txt-mute txt-small is-vishidden-narrow" href="#" rel="viewDetails">{{_i}}Details{{/i}}</a> <a class="pull-left margin-txs txt-mute txt-small is-vishidden is-visshown-narrow" href="#" rel="viewDetails">{{_i}}Open{{/i}}</a> {{/inReplyToID}} {{#inReplyToID}}');
|
||||
footer = footer.replace('<span class="link-complex-target"> {{_i}}View Conversation{{/i}}', '<i class="icon icon-conversation icon-small-context"></i> <span class="link-complex-target"> <span class="is-vishidden-wide is-vishidden-narrow">{{_i}}View{{/i}}</span> <span class="is-vishidden is-visshown-wide">{{_i}}Conversation{{/i}}</span>');
|
||||
TD.mustaches["status/tweet_single_footer.mustache"] = footer;
|
||||
}
|
||||
|
||||
if (config.moveTweetActionsToRight){
|
||||
$(document).on("uiShowActionsMenu", this.uiShowActionsMenuEvent);
|
||||
}
|
||||
});
|
||||
|
||||
// fix layout for right-aligned actions menu
|
||||
this.uiShowActionsMenuEvent = function(){
|
||||
$(".js-dropdown.pos-r").toggleClass("pos-r pos-l");
|
||||
};
|
||||
}
|
||||
|
||||
ready(){
|
||||
$(document).on("uiShowActionsMenu", this.uiShowActionsMenuEvent);
|
||||
}
|
||||
|
||||
disabled(){
|
||||
this.css.remove();
|
||||
TD.mustaches["status/tweet_single_footer.mustache"] = this.prevFooterMustache;
|
||||
|
||||
$(document).off("uiShowActionsMenu", this.uiShowActionsMenuEvent);
|
||||
TD.mustaches["status/tweet_single_footer.mustache"] = this.prevFooterMustache;
|
||||
}
|
||||
}
|
||||
|
22
Resources/Plugins/design-revert/configuration.default.js
Normal file
22
Resources/Plugins/design-revert/configuration.default.js
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
/*
|
||||
* Hides the action bar below each tweet.
|
||||
* The action bar fully appears when the mouse is over the tweet, or at half the opacity when the tweet is liked/retweeted.
|
||||
*/
|
||||
hideTweetActions: true,
|
||||
|
||||
/*
|
||||
* Moves the action bar to the right side of the tweet.
|
||||
*/
|
||||
moveTweetActionsToRight: true,
|
||||
|
||||
/*
|
||||
* Reverts New Tweet font size to a smaller one.
|
||||
*/
|
||||
smallComposeTextSize: false,
|
||||
|
||||
/*
|
||||
* Reverts design changes to the 'View Conversation' and 'Details' links.
|
||||
*/
|
||||
revertConversationLinks: true
|
||||
}
|
18
Resources/Plugins/emoji-keyboard/.meta
Normal file
18
Resources/Plugins/emoji-keyboard/.meta
Normal file
@@ -0,0 +1,18 @@
|
||||
[name]
|
||||
Emoji keyboard
|
||||
|
||||
[description]
|
||||
- Adds an emoji keyboard when writing tweets
|
||||
- Emoji list provided by http://unicode.org/emoji/charts/emoji-ordering.html
|
||||
|
||||
[author]
|
||||
chylex
|
||||
|
||||
[version]
|
||||
1.0
|
||||
|
||||
[website]
|
||||
https://tweetduck.chylex.com
|
||||
|
||||
[requires]
|
||||
1.5.3
|
178
Resources/Plugins/emoji-keyboard/browser.js
Normal file
178
Resources/Plugins/emoji-keyboard/browser.js
Normal file
@@ -0,0 +1,178 @@
|
||||
enabled(){
|
||||
this.emojiHTML = "";
|
||||
|
||||
var me = this;
|
||||
|
||||
// styles
|
||||
|
||||
this.css = window.TDPF_createCustomStyle(this);
|
||||
this.css.insert(".emoji-keyboard { position: absolute; width: 15.35em; height: 11.2em; background-color: white; overflow-y: auto; padding: 0.1em; box-sizing: border-box; border-radius: 2px; font-size: 24px; z-index: 9999 }");
|
||||
this.css.insert(".emoji-keyboard .separator { height: 26px; }");
|
||||
this.css.insert(".emoji-keyboard .emoji { padding: 0.1em !important; cursor: pointer }");
|
||||
|
||||
// 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>';
|
||||
|
||||
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);
|
||||
|
||||
var dockedComposePanel = $(".js-docked-compose");
|
||||
|
||||
if (dockedComposePanel.length){
|
||||
dockedComposePanel.find(".cf.margin-t--12.margin-b--30").first().append(buttonHTML);
|
||||
}
|
||||
|
||||
// keyboard generation
|
||||
|
||||
this.currentKeyboard = null;
|
||||
|
||||
var hideKeyboard = () => {
|
||||
$(this.currentKeyboard).remove();
|
||||
this.currentKeyboard = null;
|
||||
|
||||
$(".emoji-keyboard-popup-btn").removeClass("is-selected");
|
||||
};
|
||||
|
||||
this.generateKeyboardFor = (input, left, top) => {
|
||||
var created = document.createElement("div");
|
||||
document.body.appendChild(created);
|
||||
|
||||
created.classList.add("emoji-keyboard");
|
||||
created.style.left = left+"px";
|
||||
created.style.top = top+"px";
|
||||
created.innerHTML = this.emojiHTML;
|
||||
|
||||
created.addEventListener("click", function(e){
|
||||
if (e.target.tagName === "IMG"){
|
||||
input.val(input.val()+e.target.getAttribute("alt"));
|
||||
input.trigger("change");
|
||||
input.focus();
|
||||
}
|
||||
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
return created;
|
||||
};
|
||||
|
||||
this.prevTryPasteImage = window.TDGF_tryPasteImage;
|
||||
var prevTryPasteImageF = this.prevTryPasteImage;
|
||||
|
||||
window.TDGF_tryPasteImage = function(){
|
||||
if (me.currentKeyboard){
|
||||
hideKeyboard();
|
||||
}
|
||||
|
||||
return prevTryPasteImageF.apply(this, arguments);
|
||||
};
|
||||
|
||||
// event handlers
|
||||
|
||||
this.emojiKeyboardButtonClickEvent = function(e){
|
||||
if (me.currentKeyboard){
|
||||
hideKeyboard();
|
||||
}
|
||||
else{
|
||||
var pos = $(this).offset();
|
||||
me.currentKeyboard = me.generateKeyboardFor($(".js-compose-text").first(), pos.left, pos.top+$(this).outerHeight()+8);
|
||||
|
||||
$(this).addClass("is-selected");
|
||||
}
|
||||
|
||||
$(this).blur();
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
this.documentClickEvent = function(e){
|
||||
if (me.currentKeyboard && !e.target.classList.contains("js-compose-text")){
|
||||
hideKeyboard();
|
||||
}
|
||||
};
|
||||
|
||||
this.documentKeyEvent = function(e){
|
||||
if (me.currentKeyboard && e.keyCode === 27){ // escape
|
||||
hideKeyboard();
|
||||
e.stopPropagation();
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* TODO
|
||||
* ----
|
||||
* add emoji search if I can be bothered
|
||||
* lazy emoji loading
|
||||
*/
|
||||
}
|
||||
|
||||
ready(){
|
||||
$(".emoji-keyboard-popup-btn").on("click", this.emojiKeyboardButtonClickEvent);
|
||||
$(document).on("click", this.documentClickEvent);
|
||||
$(document).on("keydown", this.documentKeyEvent);
|
||||
|
||||
// HTML generation
|
||||
|
||||
var convUnicode = function(codePt){
|
||||
if (codePt > 0xFFFF){
|
||||
codePt -= 0x10000;
|
||||
return String.fromCharCode(0xD800+(codePt>>10), 0xDC00+(codePt&0x3FF));
|
||||
}
|
||||
else{
|
||||
return String.fromCharCode(codePt);
|
||||
}
|
||||
};
|
||||
|
||||
$TDP.readFileRoot(this.$token, "emoji-ordering.txt").then(contents => {
|
||||
let generated = [];
|
||||
|
||||
let addDeclaration = decl => {
|
||||
generated.push(decl.split(" ").map(pt => convUnicode(parseInt(pt, 16))).join(""));
|
||||
};
|
||||
|
||||
let skinTones = [
|
||||
"1F3FB", "1F3FC", "1F3FD", "1F3FE", "1F3FF"
|
||||
];
|
||||
|
||||
for(let line of contents.split("\n")){
|
||||
if (line[0] === '@'){
|
||||
generated.push("___");
|
||||
}
|
||||
else{
|
||||
let decl = line.slice(0, line.indexOf(";"));
|
||||
let skinIndex = decl.indexOf('$');
|
||||
|
||||
if (skinIndex !== -1){
|
||||
let declPre = decl.slice(0, skinIndex);
|
||||
let declPost = decl.slice(skinIndex+1);
|
||||
|
||||
skinTones.map(skinTone => declPre+skinTone+declPost).forEach(addDeclaration);
|
||||
}
|
||||
else{
|
||||
addDeclaration(decl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let start = "<p style='font-size:13px;color:#444;margin:4px;text-align:center'>Please, note that most emoji will not show up properly in the text box above, but they will display in the tweet.</p>";
|
||||
this.emojiHTML = start+TD.util.cleanWithEmoji(generated.join("")).replace(/___/g, "<div class='separator'></div>");
|
||||
}).catch(err => {
|
||||
$TD.alert("error", "Problem loading emoji keyboard: "+err.message);
|
||||
});
|
||||
}
|
||||
|
||||
disabled(){
|
||||
this.css.remove();
|
||||
|
||||
if (this.currentKeyboard){
|
||||
$(this.currentKeyboard).remove();
|
||||
}
|
||||
|
||||
window.TDGF_tryPasteImage = this.prevTryPasteImage;
|
||||
|
||||
$(".emoji-keyboard-popup-btn").off("click", this.emojiKeyboardButtonClickEvent);
|
||||
$(".emoji-keyboard-popup-btn").remove();
|
||||
|
||||
$(document).off("click", this.documentClickEvent);
|
||||
$(document).off("keydown", this.documentKeyEvent);
|
||||
TD.mustaches["compose/docked_compose.mustache"] = this.prevComposeMustache;
|
||||
}
|
1671
Resources/Plugins/emoji-keyboard/emoji-ordering.txt
Normal file
1671
Resources/Plugins/emoji-keyboard/emoji-ordering.txt
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
(function($, $TD, TD){
|
||||
(function($, $TD, $TDX, TD){
|
||||
//
|
||||
// Variable: Current highlighted column jQuery object.
|
||||
//
|
||||
@@ -10,71 +10,14 @@
|
||||
var highlightedTweetEle;
|
||||
|
||||
//
|
||||
// Function: Initializes TweetD*ck events. Called after the website app is loaded.
|
||||
// Variable: Array of functions called after the website app is loaded.
|
||||
//
|
||||
var initializeTweetDck = function(){
|
||||
// Settings button hook
|
||||
$("[data-action='settings-menu']").click(function(){
|
||||
setTimeout(function(){
|
||||
var menu = $(".js-dropdown-content").children("ul").first();
|
||||
if (menu.length === 0)return;
|
||||
|
||||
menu.children(".drp-h-divider").last().after([
|
||||
'<li class="is-selectable" data-std><a href="#" data-action="td-settings">'+$TD.brandName+' settings</a></li>',
|
||||
'<li class="is-selectable" data-std><a href="#" data-action="td-plugins">'+$TD.brandName+' plugins</a></li>',
|
||||
'<li class="drp-h-divider"></li>'
|
||||
].join(""));
|
||||
|
||||
var buttons = menu.children("[data-std]");
|
||||
|
||||
buttons.on("click", "a", function(){
|
||||
var action = $(this).attr("data-action");
|
||||
|
||||
if (action === "td-settings"){
|
||||
$TD.openSettingsMenu();
|
||||
}
|
||||
else if (action === "td-plugins"){
|
||||
$TD.openPluginsMenu();
|
||||
}
|
||||
});
|
||||
|
||||
buttons.hover(function(){
|
||||
$(this).addClass("is-selected");
|
||||
}, function(){
|
||||
$(this).removeClass("is-selected");
|
||||
});
|
||||
}, 0);
|
||||
});
|
||||
|
||||
// Notification handling
|
||||
$.subscribe("/notifications/new", function(obj){
|
||||
for(let index = obj.items.length-1; index >= 0; index--){
|
||||
onNewTweet(obj.column, obj.items[index]);
|
||||
}
|
||||
});
|
||||
|
||||
// Setup video element replacement and fix missing target in user links
|
||||
new MutationObserver(function(){
|
||||
$("video").each(function(){
|
||||
$(this).parent().replaceWith("<a href='"+$(this).attr("src")+"' rel='url' target='_blank' style='display:block; border:1px solid #555; padding:3px 6px'>► Open video in browser</a>");
|
||||
});
|
||||
|
||||
$("a[rel='user']").attr("target", "_blank");
|
||||
}).observe($(".js-app-columns")[0], {
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
|
||||
// Finish init and load plugins
|
||||
$TD.loadFontSizeClass(TD.settings.getFontSize());
|
||||
$TD.loadNotificationHeadContents(getNotificationHeadContents());
|
||||
|
||||
window.TD_APP_READY = true;
|
||||
|
||||
if (window.TD_PLUGINS){
|
||||
window.TD_PLUGINS.onReady();
|
||||
}
|
||||
};
|
||||
var onAppReady = [];
|
||||
|
||||
//
|
||||
// Variable: DOM object containing the main app element.
|
||||
//
|
||||
var app = $(document.body).children(".js-app");
|
||||
|
||||
//
|
||||
// Function: Prepends code at the beginning of a function. If the prepended function returns true, execution of the original function is cancelled.
|
||||
@@ -112,6 +55,7 @@
|
||||
|
||||
html.css("border", "0");
|
||||
html.find("footer").last().remove(); // apparently withTweetActions breaks for certain tweets, nice
|
||||
html.find(".js-quote-detail").removeClass("is-actionable");
|
||||
|
||||
var url = html.find("time").first().children("a").first().attr("href") || "";
|
||||
|
||||
@@ -136,23 +80,6 @@
|
||||
return tags.join("");
|
||||
};
|
||||
|
||||
//
|
||||
// Block: Observe the app <div> element and initialize TweetD*ck whenever possible.
|
||||
//
|
||||
var app = $("body").children(".js-app");
|
||||
|
||||
new MutationObserver(function(){
|
||||
if (window.TD_APP_READY && app.hasClass("is-hidden")){
|
||||
window.TD_APP_READY = false;
|
||||
}
|
||||
else if (!window.TD_APP_READY && !app.hasClass("is-hidden")){
|
||||
initializeTweetDck();
|
||||
}
|
||||
}).observe(app[0], {
|
||||
attributes: true,
|
||||
attributeFilter: [ "class" ]
|
||||
});
|
||||
|
||||
//
|
||||
// Block: Hook into settings object to detect when the settings change.
|
||||
//
|
||||
@@ -167,7 +94,7 @@
|
||||
});
|
||||
|
||||
//
|
||||
// Block: Force popup notification settings.
|
||||
// Block: Enable popup notifications.
|
||||
//
|
||||
TD.controller.notifications.hasNotifications = function(){
|
||||
return true;
|
||||
@@ -177,6 +104,49 @@
|
||||
return true;
|
||||
};
|
||||
|
||||
$.subscribe("/notifications/new", function(obj){
|
||||
for(let index = obj.items.length-1; index >= 0; index--){
|
||||
onNewTweet(obj.column, obj.items[index]);
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
// Block: Add TweetDuck buttons to the settings menu.
|
||||
//
|
||||
onAppReady.push(function(){
|
||||
$("[data-action='settings-menu']").click(function(){
|
||||
setTimeout(function(){
|
||||
var menu = $(".js-dropdown-content").children("ul").first();
|
||||
if (menu.length === 0)return;
|
||||
|
||||
menu.children(".drp-h-divider").last().after([
|
||||
'<li class="is-selectable" data-std><a href="#" data-action="td-settings">TweetDuck settings</a></li>',
|
||||
'<li class="is-selectable" data-std><a href="#" data-action="td-plugins">TweetDuck plugins</a></li>',
|
||||
'<li class="drp-h-divider"></li>'
|
||||
].join(""));
|
||||
|
||||
var buttons = menu.children("[data-std]");
|
||||
|
||||
buttons.on("click", "a", function(){
|
||||
var action = $(this).attr("data-action");
|
||||
|
||||
if (action === "td-settings"){
|
||||
$TD.openSettingsMenu();
|
||||
}
|
||||
else if (action === "td-plugins"){
|
||||
$TD.openPluginsMenu();
|
||||
}
|
||||
});
|
||||
|
||||
buttons.hover(function(){
|
||||
$(this).addClass("is-selected");
|
||||
}, function(){
|
||||
$(this).removeClass("is-selected");
|
||||
});
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
|
||||
//
|
||||
// Block: Expand shortened links on hover or display tooltip.
|
||||
//
|
||||
@@ -198,7 +168,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if ($TD.expandLinksOnHover){
|
||||
if ($TDX.expandLinksOnHover){
|
||||
tooltipTimer = window.setTimeout(function(){
|
||||
var expanded = me.attr("data-full-url");
|
||||
expanded = cutStart(expanded, "https://");
|
||||
@@ -217,7 +187,7 @@
|
||||
}
|
||||
}
|
||||
else if (e.type === "mouseleave"){
|
||||
if ($TD.expandLinksOnHover){
|
||||
if ($TDX.expandLinksOnHover){
|
||||
var prevText = me.attr("td-prev-text");
|
||||
|
||||
if (prevText){
|
||||
@@ -256,7 +226,7 @@
|
||||
var soundEle = document.getElementById("update-sound");
|
||||
|
||||
soundEle.play = prependToFunction(soundEle.play, function(){
|
||||
return $TD.muteNotifications || $TD.hasCustomNotificationSound;
|
||||
return $TDX.muteNotifications || $TDX.hasCustomNotificationSound;
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -374,6 +344,11 @@
|
||||
};
|
||||
|
||||
var clickUpload = function(){
|
||||
$(document).one("uiFilesAdded", function(){
|
||||
getScroller().scrollTop(prevScrollTop);
|
||||
$(".js-drawer").find(".js-compose-text").first()[0].focus();
|
||||
});
|
||||
|
||||
var button = $(".js-add-image-button").first();
|
||||
|
||||
var scroller = getScroller();
|
||||
@@ -386,7 +361,7 @@
|
||||
$TD.clickUploadImage(Math.floor(buttonPos.left), Math.floor(buttonPos.top));
|
||||
};
|
||||
|
||||
$(".js-app").delegate(".js-compose-text,.js-reply-tweetbox", "paste", function(){
|
||||
app.delegate(".js-compose-text,.js-reply-tweetbox", "paste", function(){
|
||||
lastPasteElement = $(this);
|
||||
$TD.tryPasteImage();
|
||||
});
|
||||
@@ -425,13 +400,6 @@
|
||||
lastPasteElement = null;
|
||||
}
|
||||
};
|
||||
|
||||
window.TDGF_tryPasteImageFinish = function(){
|
||||
setTimeout(function(){
|
||||
getScroller().scrollTop(prevScrollTop);
|
||||
$(".js-drawer").find(".js-compose-text").first()[0].focus();
|
||||
}, 10);
|
||||
};
|
||||
})();
|
||||
|
||||
//
|
||||
@@ -524,6 +492,25 @@
|
||||
});
|
||||
})();
|
||||
|
||||
//
|
||||
// Block: Swap shift key functionality for selecting accounts.
|
||||
//
|
||||
onAppReady.push(function(){
|
||||
$(".js-drawer[data-drawer='compose']").delegate(".js-account-list > .js-account-item", "click", function(e){
|
||||
e.shiftKey = !e.shiftKey;
|
||||
});
|
||||
|
||||
TD.components.AccountSelector.prototype.refreshPostingAccounts = appendToFunction(TD.components.AccountSelector.prototype.refreshPostingAccounts, function(){
|
||||
if (!this.$node.attr("td-account-selector-hook")){
|
||||
this.$node.attr("td-account-selector-hook", "1");
|
||||
|
||||
this.$node.delegate(".js-account-item", "click", function(e){
|
||||
e.shiftKey = !e.shiftKey;
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
//
|
||||
// Block: Work around clipboard HTML formatting.
|
||||
//
|
||||
@@ -543,12 +530,79 @@
|
||||
styleOfficial.sheet.insertRule(".txt-base-smallest .badge-verified:before { height: 13px !important; }", 0); // fix cut off badge icon
|
||||
styleOfficial.sheet.insertRule(".keyboard-shortcut-list { vertical-align: top; }", 0); // fix keyboard navigation alignment
|
||||
|
||||
if ($TD.hasCustomBrowserCSS){
|
||||
var styleCustom = document.createElement("style");
|
||||
styleCustom.innerHTML = $TD.customBrowserCSS;
|
||||
document.head.appendChild(styleCustom);
|
||||
}
|
||||
styleOfficial.sheet.insertRule(".is-video a:not([href*='youtu']), .is-gif .js-media-gif-container { cursor: alias; }", 0); // change cursor on unsupported videos
|
||||
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
|
||||
|
||||
TD.services.TwitterActionRetweetedRetweet.prototype.iconClass = "icon-retweet icon-retweet-color txt-base-medium"; // fix retweet icon mismatch
|
||||
|
||||
window.TDGF_reinjectCustomCSS = function(styles){
|
||||
$("#tweetduck-custom-css").remove();
|
||||
|
||||
if (styles && styles.length){
|
||||
$(document.head).append("<style type='text/css' id='tweetduck-custom-css'>"+styles+"</style>");
|
||||
}
|
||||
};
|
||||
})();
|
||||
})($, $TD, TD);
|
||||
|
||||
//
|
||||
// Block: Setup unsupported video element hook.
|
||||
//
|
||||
(function(){
|
||||
var cancelModal = false;
|
||||
|
||||
TD.components.MediaGallery.prototype._loadTweet = appendToFunction(TD.components.MediaGallery.prototype._loadTweet, function(){
|
||||
var media = this.chirp.getMedia().find(media => media.mediaId === this.clickedMediaEntityId);
|
||||
|
||||
if (media && media.isVideo && media.service !== "youtube"){
|
||||
$TD.openBrowser(this.clickedLink);
|
||||
cancelModal = true;
|
||||
}
|
||||
});
|
||||
|
||||
TD.components.BaseModal.prototype.setAndShowContainer = prependToFunction(TD.components.BaseModal.prototype.setAndShowContainer, function(){
|
||||
if (cancelModal){
|
||||
cancelModal = false;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
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: Finish initialization and load plugins.
|
||||
//
|
||||
onAppReady.push(function(){
|
||||
$TD.loadFontSizeClass(TD.settings.getFontSize());
|
||||
$TD.loadNotificationHeadContents(getNotificationHeadContents());
|
||||
|
||||
if (window.TD_PLUGINS){
|
||||
window.TD_PLUGINS.onReady();
|
||||
}
|
||||
});
|
||||
|
||||
//
|
||||
// Block: Observe the main app element and call the ready event whenever the contents are loaded.
|
||||
//
|
||||
new MutationObserver(function(){
|
||||
if (window.TD_APP_READY && app.hasClass("is-hidden")){
|
||||
window.TD_APP_READY = false;
|
||||
}
|
||||
else if (!window.TD_APP_READY && !app.hasClass("is-hidden")){
|
||||
onAppReady.forEach(func => func());
|
||||
window.TD_APP_READY = true;
|
||||
}
|
||||
}).observe(app[0], {
|
||||
attributes: true,
|
||||
attributeFilter: [ "class" ]
|
||||
});
|
||||
})($, $TD, $TDX, TD);
|
||||
|
@@ -1,19 +1,49 @@
|
||||
(function($, $TD, TD){
|
||||
(function($, $TD, $TDX, TD){
|
||||
var isDebugging = false;
|
||||
|
||||
$(document).keydown(function(e){
|
||||
|
||||
// ==============================
|
||||
// F4 key - simulate notification
|
||||
// ==============================
|
||||
// ==========================
|
||||
// F4 key - toggle debug mode
|
||||
// ==========================
|
||||
|
||||
if (e.keyCode === 115){
|
||||
var col = TD.controller.columnManager.getAllOrdered()[0];
|
||||
isDebugging = !isDebugging;
|
||||
$(".app-title").first().css("background-color", isDebugging ? "#5A6B75" : "#292F33");
|
||||
}
|
||||
|
||||
// Debug mode handling
|
||||
|
||||
else if (isDebugging){
|
||||
e.preventDefault();
|
||||
|
||||
$.publish("/notifications/new",[{
|
||||
column: col,
|
||||
items: [
|
||||
col.updateArray[Math.floor(Math.random()*col.updateArray.length)]
|
||||
]
|
||||
}]);
|
||||
// ===================================
|
||||
// N key - simulate popup notification
|
||||
// ===================================
|
||||
|
||||
if (e.keyCode === 78){
|
||||
var col = TD.controller.columnManager.getAllOrdered()[0];
|
||||
|
||||
$.publish("/notifications/new",[{
|
||||
column: col,
|
||||
items: [
|
||||
col.updateArray[Math.floor(Math.random()*col.updateArray.length)]
|
||||
]
|
||||
}]);
|
||||
}
|
||||
|
||||
// ===================================
|
||||
// S key - simulate sound notification
|
||||
// ===================================
|
||||
|
||||
else if (e.keyCode === 83){
|
||||
if ($TDX.hasCustomNotificationSound){
|
||||
$TD.onTweetSound();
|
||||
}
|
||||
else{
|
||||
document.getElementById("update-sound").play();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
})($, $TD, TD);
|
||||
})($, $TD, $TDX, TD);
|
||||
|
@@ -1,4 +1,4 @@
|
||||
(function($TD){
|
||||
(function($TD, $TDX){
|
||||
//
|
||||
// Variable: Collection of all <a> tags.
|
||||
//
|
||||
@@ -51,7 +51,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if ($TD.expandLinksOnHover){
|
||||
if ($TDX.expandLinksOnHover){
|
||||
tooltipTimer = window.setTimeout(function(){
|
||||
var expanded = url;
|
||||
expanded = cutStart(expanded, "https://");
|
||||
@@ -73,7 +73,7 @@
|
||||
addEventListener(links, "mouseleave", function(e){
|
||||
if (!e.currentTarget.hasAttribute("data-full-url"))return;
|
||||
|
||||
if ($TD.expandLinksOnHover){
|
||||
if ($TDX.expandLinksOnHover){
|
||||
var prevText = e.currentTarget.getAttribute("td-prev-text");
|
||||
|
||||
if (prevText){
|
||||
@@ -146,4 +146,4 @@
|
||||
document.body.addEventListener("mouseleave", function(){
|
||||
document.body.classList.remove("td-hover");
|
||||
});
|
||||
})($TD);
|
||||
})($TD, $TDX);
|
||||
|
@@ -7,33 +7,38 @@
|
||||
//
|
||||
// Constant: Update exe file name.
|
||||
//
|
||||
const updateFileName = $TDU.brandName+".Update.exe";
|
||||
const updateFileName = "TweetDuck.Update.exe";
|
||||
|
||||
//
|
||||
// Constant: Url that returns JSON data about latest version.
|
||||
//
|
||||
const updateCheckUrlLatest = "https://api.github.com/repos/chylex/"+$TDU.brandName+"/releases/latest";
|
||||
const updateCheckUrlLatest = "https://api.github.com/repos/chylex/TweetDuck/releases/latest";
|
||||
|
||||
//
|
||||
// Constant: Url that returns JSON data about all versions, including prereleases.
|
||||
//
|
||||
const updateCheckUrlAll = "https://api.github.com/repos/chylex/"+$TDU.brandName+"/releases";
|
||||
const updateCheckUrlAll = "https://api.github.com/repos/chylex/TweetDuck/releases";
|
||||
|
||||
//
|
||||
// Constant: Fallback url in case the update installer file is missing.
|
||||
//
|
||||
const updateDownloadFallback = "https://tweetduck.chylex.com/#download";
|
||||
|
||||
//
|
||||
// Function: Creates the update notification element. Removes the old one if already exists.
|
||||
//
|
||||
var createUpdateNotificationElement = function(version, download){
|
||||
var displayNotification = function(version, download){
|
||||
var outdated = version === "unsupported";
|
||||
|
||||
var ele = $("#tweetdck-update");
|
||||
var ele = $("#tweetduck-update");
|
||||
var existed = ele.length > 0;
|
||||
|
||||
if (existed > 0){
|
||||
if (existed){
|
||||
ele.remove();
|
||||
}
|
||||
|
||||
var html = outdated ? [
|
||||
"<div id='tweetdck-update'>",
|
||||
"<div id='tweetduck-update'>",
|
||||
"<p class='tdu-title'>Unsupported System</p>",
|
||||
"<p class='tdu-info'>You will not receive updates.</p>",
|
||||
"<div class='tdu-buttons'>",
|
||||
@@ -42,8 +47,8 @@
|
||||
"</div>",
|
||||
"</div>"
|
||||
] : [
|
||||
"<div id='tweetdck-update'>",
|
||||
"<p class='tdu-title'>"+$TDU.brandName+" Update</p>",
|
||||
"<div id='tweetduck-update'>",
|
||||
"<p class='tdu-title'>TweetDuck Update</p>",
|
||||
"<p class='tdu-info'>Version "+version+" is now available.</p>",
|
||||
"<div class='tdu-buttons'>",
|
||||
"<button class='btn btn-positive tdu-btn-download'><span class='label'>Download</span></button>",
|
||||
@@ -54,7 +59,7 @@
|
||||
|
||||
$(document.body).append(html.join(""));
|
||||
|
||||
ele = $("#tweetdck-update");
|
||||
ele = $("#tweetduck-update");
|
||||
|
||||
var buttonDiv = ele.children("div.tdu-buttons").first();
|
||||
|
||||
@@ -106,7 +111,13 @@
|
||||
|
||||
buttonDiv.children(".tdu-btn-download").click(function(){
|
||||
ele.remove();
|
||||
$TDU.onUpdateAccepted(version, download);
|
||||
|
||||
if (download){
|
||||
$TDU.onUpdateAccepted(version, download);
|
||||
}
|
||||
else{
|
||||
$TDU.openBrowser(updateDownloadFallback);
|
||||
}
|
||||
});
|
||||
|
||||
buttonDiv.children(".tdu-btn-unsupported").click(function(){
|
||||
@@ -125,36 +136,31 @@
|
||||
return ele;
|
||||
};
|
||||
|
||||
//
|
||||
// Function: Returns milliseconds until the start of the next hour, with an offset in seconds that can skip an hour if the clock would roll over too soon.
|
||||
//
|
||||
var getTimeUntilNextHour = function(offset){
|
||||
var now = new Date();
|
||||
var offset = new Date(+now+offset*1000);
|
||||
return new Date(offset.getFullYear(), offset.getMonth(), offset.getDate(), offset.getHours()+1, 0, 0)-now;
|
||||
};
|
||||
|
||||
//
|
||||
// Function: Runs an update check and updates all DOM elements appropriately.
|
||||
//
|
||||
var runUpdateCheck = function(force, eventID){
|
||||
if (!$TDU.isSystemSupported){
|
||||
if ($TDU.dismissedVersionTag !== "unsupported"){
|
||||
createUpdateNotificationElement("unsupported");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var runUpdateCheck = function(eventID, versionTag, dismissedVersionTag, allowPre){
|
||||
clearTimeout(updateCheckTimeoutID);
|
||||
updateCheckTimeoutID = setTimeout(runUpdateCheck, 1000*60*60); // 1 hour
|
||||
|
||||
if (!$TDU.updateCheckEnabled && !force){
|
||||
return;
|
||||
}
|
||||
|
||||
var allowPre = $TDU.allowPreReleases;
|
||||
updateCheckTimeoutID = setTimeout($TDU.triggerUpdateCheck, getTimeUntilNextHour(60*30)); // 30 minute offset
|
||||
|
||||
$.getJSON(allowPre ? updateCheckUrlAll : updateCheckUrlLatest, function(response){
|
||||
var release = allowPre ? response[0] : response;
|
||||
|
||||
var tagName = release.tag_name;
|
||||
var hasUpdate = tagName !== $TDU.versionTag && tagName !== $TDU.dismissedVersionTag && release.assets.length > 0;
|
||||
var hasUpdate = tagName !== versionTag && tagName !== dismissedVersionTag && release.assets.length > 0;
|
||||
|
||||
if (hasUpdate){
|
||||
var obj = release.assets.find(asset => asset.name === updateFileName) || release.assets[0];
|
||||
createUpdateNotificationElement(tagName, obj.browser_download_url);
|
||||
var obj = release.assets.find(asset => asset.name === updateFileName) || { browser_download_url: "" };
|
||||
displayNotification(tagName, obj.browser_download_url);
|
||||
}
|
||||
|
||||
if (eventID){ // ignore undefined and 0
|
||||
@@ -166,6 +172,6 @@
|
||||
//
|
||||
// Block: Setup global functions.
|
||||
//
|
||||
window.TDUF_displayNotification = displayNotification;
|
||||
window.TDUF_runUpdateCheck = runUpdateCheck;
|
||||
runUpdateCheck();
|
||||
})($, $TDU);
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="packages\CefSharp.WinForms.53.0.1\build\CefSharp.WinForms.props" Condition="Exists('packages\CefSharp.WinForms.53.0.1\build\CefSharp.WinForms.props')" />
|
||||
<Import Project="packages\CefSharp.Common.53.0.1\build\CefSharp.Common.props" Condition="Exists('packages\CefSharp.Common.53.0.1\build\CefSharp.Common.props')" />
|
||||
<Import Project="packages\CefSharp.WinForms.55.0.0\build\CefSharp.WinForms.props" Condition="Exists('packages\CefSharp.WinForms.55.0.0\build\CefSharp.WinForms.props')" />
|
||||
<Import Project="packages\CefSharp.Common.55.0.0\build\CefSharp.Common.props" Condition="Exists('packages\CefSharp.Common.55.0.0\build\CefSharp.Common.props')" />
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
@@ -10,11 +10,10 @@
|
||||
<OutputType>WinExe</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>TweetDck</RootNamespace>
|
||||
<AssemblyName Condition=" '$(Configuration)' == 'Debug' ">TweetDick</AssemblyName>
|
||||
<AssemblyName Condition=" '$(Configuration)' == 'Release' ">TweetDuck</AssemblyName>
|
||||
<AssemblyName>TweetDuck</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<NuGetPackageImportStamp>783c0e30</NuGetPackageImportStamp>
|
||||
<NuGetPackageImportStamp>886d3074</NuGetPackageImportStamp>
|
||||
<TargetFrameworkProfile>
|
||||
</TargetFrameworkProfile>
|
||||
<PublishUrl>publish\</PublishUrl>
|
||||
@@ -59,9 +58,6 @@
|
||||
</DefineConstants>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<AssemblyName>TweetDuck</AssemblyName>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
@@ -71,6 +67,7 @@
|
||||
<ItemGroup>
|
||||
<Compile Include="Configuration\LockManager.cs" />
|
||||
<Compile Include="Configuration\UserConfig.cs" />
|
||||
<Compile Include="Core\Bridge\PropertyBridge.cs" />
|
||||
<Compile Include="Core\Controls\ControlExtensions.cs" />
|
||||
<Compile Include="Core\Controls\FlatButton.cs">
|
||||
<SubType>Component</SubType>
|
||||
@@ -112,6 +109,7 @@
|
||||
<Compile Include="Core\Handling\FileDialogHandler.cs" />
|
||||
<Compile Include="Core\Handling\JavaScriptDialogHandler.cs" />
|
||||
<Compile Include="Core\Handling\LifeSpanHandler.cs" />
|
||||
<Compile Include="Core\Notification\SoundNotification.cs" />
|
||||
<Compile Include="Core\Notification\TweetNotification.cs" />
|
||||
<Compile Include="Core\Other\FormAbout.cs">
|
||||
<SubType>Form</SubType>
|
||||
@@ -190,6 +188,7 @@
|
||||
</Compile>
|
||||
<Compile Include="Core\Notification\NotificationFlags.cs" />
|
||||
<Compile Include="Core\Notification\Screenshot\TweetScreenshotManager.cs" />
|
||||
<Compile Include="Core\Utils\TwoKeyDictionary.cs" />
|
||||
<Compile Include="Core\Utils\WindowState.cs" />
|
||||
<Compile Include="Core\Utils\WindowsUtils.cs" />
|
||||
<Compile Include="Core\Bridge\TweetDeckBridge.cs" />
|
||||
@@ -222,6 +221,7 @@
|
||||
<Compile Include="Plugins\PluginManager.cs" />
|
||||
<Compile Include="Plugins\PluginScriptGenerator.cs" />
|
||||
<Compile Include="Reporter.cs" />
|
||||
<Compile Include="Updates\Events\UpdateDismissedEventArgs.cs" />
|
||||
<Compile Include="Updates\FormUpdateDownload.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
@@ -238,8 +238,8 @@
|
||||
<Compile Include="Core\Utils\BrowserUtils.cs" />
|
||||
<Compile Include="Core\Utils\HardwareAcceleration.cs" />
|
||||
<Compile Include="Core\Utils\NativeMethods.cs" />
|
||||
<Compile Include="Updates\UpdateAcceptedEventArgs.cs" />
|
||||
<Compile Include="Updates\UpdateCheckEventArgs.cs" />
|
||||
<Compile Include="Updates\Events\UpdateAcceptedEventArgs.cs" />
|
||||
<Compile Include="Updates\Events\UpdateCheckEventArgs.cs" />
|
||||
<Compile Include="Updates\UpdateHandler.cs" />
|
||||
<Compile Include="Updates\UpdateInfo.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
@@ -252,7 +252,6 @@
|
||||
<Compile Include="Resources\ScriptLoader.cs" />
|
||||
<Compile Include="Updates\UpdaterSettings.cs" />
|
||||
<None Include="Configuration\app.config" />
|
||||
<None Include="Configuration\packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<BootstrapperPackage Include=".NETFramework,Version=v4.0,Profile=Client">
|
||||
@@ -304,6 +303,7 @@
|
||||
</ContentWithTargetPath>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="Configuration\packages.config" />
|
||||
<None Include="Resources\icon-small.ico" />
|
||||
<None Include="Resources\icon-tray-new.ico" />
|
||||
<None Include="Resources\icon-tray.ico" />
|
||||
@@ -325,12 +325,12 @@
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('packages\cef.redist.x86.3.2785.1486\build\cef.redist.x86.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\cef.redist.x86.3.2785.1486\build\cef.redist.x86.targets'))" />
|
||||
<Error Condition="!Exists('packages\cef.redist.x64.3.2785.1486\build\cef.redist.x64.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\cef.redist.x64.3.2785.1486\build\cef.redist.x64.targets'))" />
|
||||
<Error Condition="!Exists('packages\CefSharp.Common.53.0.1\build\CefSharp.Common.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.Common.53.0.1\build\CefSharp.Common.props'))" />
|
||||
<Error Condition="!Exists('packages\CefSharp.Common.53.0.1\build\CefSharp.Common.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.Common.53.0.1\build\CefSharp.Common.targets'))" />
|
||||
<Error Condition="!Exists('packages\CefSharp.WinForms.53.0.1\build\CefSharp.WinForms.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.WinForms.53.0.1\build\CefSharp.WinForms.props'))" />
|
||||
<Error Condition="!Exists('packages\CefSharp.WinForms.53.0.1\build\CefSharp.WinForms.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.WinForms.53.0.1\build\CefSharp.WinForms.targets'))" />
|
||||
<Error Condition="!Exists('packages\cef.redist.x86.3.2883.1552\build\cef.redist.x86.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\cef.redist.x86.3.2883.1552\build\cef.redist.x86.targets'))" />
|
||||
<Error Condition="!Exists('packages\cef.redist.x64.3.2883.1552\build\cef.redist.x64.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\cef.redist.x64.3.2883.1552\build\cef.redist.x64.targets'))" />
|
||||
<Error Condition="!Exists('packages\CefSharp.Common.55.0.0\build\CefSharp.Common.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.Common.55.0.0\build\CefSharp.Common.props'))" />
|
||||
<Error Condition="!Exists('packages\CefSharp.Common.55.0.0\build\CefSharp.Common.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.Common.55.0.0\build\CefSharp.Common.targets'))" />
|
||||
<Error Condition="!Exists('packages\CefSharp.WinForms.55.0.0\build\CefSharp.WinForms.props')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.WinForms.55.0.0\build\CefSharp.WinForms.props'))" />
|
||||
<Error Condition="!Exists('packages\CefSharp.WinForms.55.0.0\build\CefSharp.WinForms.targets')" Text="$([System.String]::Format('$(ErrorText)', 'packages\CefSharp.WinForms.55.0.0\build\CefSharp.WinForms.targets'))" />
|
||||
</Target>
|
||||
<PropertyGroup>
|
||||
<PostBuildEvent>del "$(TargetPath).config"
|
||||
@@ -349,12 +349,20 @@ mkdir "$(TargetDir)plugins\official"
|
||||
mkdir "$(TargetDir)plugins\user"
|
||||
xcopy "$(ProjectDir)Resources\Plugins\*" "$(TargetDir)plugins\official\" /E /Y
|
||||
rmdir "$(ProjectDir)\bin\Debug"
|
||||
rmdir "$(ProjectDir)\bin\Release"</PostBuildEvent>
|
||||
rmdir "$(ProjectDir)\bin\Release"
|
||||
|
||||
rmdir "$(TargetDir)plugins\official\.debug" /S /Q
|
||||
|
||||
if $(ConfigurationName) == Debug (
|
||||
rmdir "$(TargetDir)plugins\official\.debug" /S /Q
|
||||
mkdir "$(TargetDir)plugins\user\.debug"
|
||||
xcopy "$(ProjectDir)Resources\Plugins\.debug\*" "$(TargetDir)plugins\user\.debug\" /E /Y
|
||||
)</PostBuildEvent>
|
||||
</PropertyGroup>
|
||||
<Import Project="packages\cef.redist.x86.3.2785.1486\build\cef.redist.x86.targets" Condition="Exists('packages\cef.redist.x86.3.2785.1486\build\cef.redist.x86.targets')" />
|
||||
<Import Project="packages\cef.redist.x64.3.2785.1486\build\cef.redist.x64.targets" Condition="Exists('packages\cef.redist.x64.3.2785.1486\build\cef.redist.x64.targets')" />
|
||||
<Import Project="packages\CefSharp.Common.53.0.1\build\CefSharp.Common.targets" Condition="Exists('packages\CefSharp.Common.53.0.1\build\CefSharp.Common.targets')" />
|
||||
<Import Project="packages\CefSharp.WinForms.53.0.1\build\CefSharp.WinForms.targets" Condition="Exists('packages\CefSharp.WinForms.53.0.1\build\CefSharp.WinForms.targets')" />
|
||||
<Import Project="packages\cef.redist.x86.3.2883.1552\build\cef.redist.x86.targets" Condition="Exists('packages\cef.redist.x86.3.2883.1552\build\cef.redist.x86.targets')" />
|
||||
<Import Project="packages\cef.redist.x64.3.2883.1552\build\cef.redist.x64.targets" Condition="Exists('packages\cef.redist.x64.3.2883.1552\build\cef.redist.x64.targets')" />
|
||||
<Import Project="packages\CefSharp.Common.55.0.0\build\CefSharp.Common.targets" Condition="Exists('packages\CefSharp.Common.55.0.0\build\CefSharp.Common.targets')" />
|
||||
<Import Project="packages\CefSharp.WinForms.55.0.0\build\CefSharp.WinForms.targets" Condition="Exists('packages\CefSharp.WinForms.55.0.0\build\CefSharp.WinForms.targets')" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
|
12
TweetDck.sln
12
TweetDck.sln
@@ -1,21 +1,31 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 2013
|
||||
VisualStudioVersion = 12.0.40629.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetDck", "TweetDck.csproj", "{2389A7CD-E0D3-4706-8294-092929A33A2D}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests", "tests\UnitTests.csproj", "{A958FA7A-4A2C-42A7-BFA0-159343483F4E}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x86 = Debug|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{2389A7CD-E0D3-4706-8294-092929A33A2D}.Debug|Any CPU.ActiveCfg = Debug|x86
|
||||
{2389A7CD-E0D3-4706-8294-092929A33A2D}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{2389A7CD-E0D3-4706-8294-092929A33A2D}.Debug|x86.Build.0 = Debug|x86
|
||||
{2389A7CD-E0D3-4706-8294-092929A33A2D}.Debug|x86.Deploy.0 = Debug|x86
|
||||
{2389A7CD-E0D3-4706-8294-092929A33A2D}.Release|Any CPU.ActiveCfg = Release|x86
|
||||
{2389A7CD-E0D3-4706-8294-092929A33A2D}.Release|x86.ActiveCfg = Release|x86
|
||||
{2389A7CD-E0D3-4706-8294-092929A33A2D}.Release|x86.Build.0 = Release|x86
|
||||
{A958FA7A-4A2C-42A7-BFA0-159343483F4E}.Debug|Any CPU.ActiveCfg = Debug|x86
|
||||
{A958FA7A-4A2C-42A7-BFA0-159343483F4E}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{A958FA7A-4A2C-42A7-BFA0-159343483F4E}.Debug|x86.Build.0 = Debug|x86
|
||||
{A958FA7A-4A2C-42A7-BFA0-159343483F4E}.Release|Any CPU.ActiveCfg = Release|x86
|
||||
{A958FA7A-4A2C-42A7-BFA0-159343483F4E}.Release|x86.ActiveCfg = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
2
TweetDck.sln.DotSettings
Normal file
2
TweetDck.sln.DotSettings
Normal file
@@ -0,0 +1,2 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/FilterSettingsManager/CoverageFilterXml/@EntryValue"><data><IncludeFilters /><ExcludeFilters><Filter ModuleMask="UnitTests" ModuleVersionMask="*" ClassMask="*" FunctionMask="*" IsEnabled="True" /></ExcludeFilters></data></s:String></wpf:ResourceDictionary>
|
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
|
||||
namespace TweetDck.Updates{
|
||||
namespace TweetDck.Updates.Events{
|
||||
class UpdateAcceptedEventArgs : EventArgs{
|
||||
public readonly UpdateInfo UpdateInfo;
|
||||
|
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
|
||||
namespace TweetDck.Updates{
|
||||
namespace TweetDck.Updates.Events{
|
||||
class UpdateCheckEventArgs : EventArgs{
|
||||
public int EventId { get; private set; }
|
||||
public bool UpdateAvailable { get; private set; }
|
||||
@@ -12,4 +12,4 @@ namespace TweetDck.Updates{
|
||||
LatestVersion = latestVersion;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
Updates/Events/UpdateDismissedEventArgs.cs
Normal file
11
Updates/Events/UpdateDismissedEventArgs.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace TweetDck.Updates.Events{
|
||||
class UpdateDismissedEventArgs : EventArgs{
|
||||
public readonly string VersionTag;
|
||||
|
||||
public UpdateDismissedEventArgs(string versionTag){
|
||||
this.VersionTag = versionTag;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Windows.Forms;
|
||||
@@ -9,7 +8,7 @@ using TweetDck.Core.Utils;
|
||||
|
||||
namespace TweetDck.Updates{
|
||||
sealed partial class FormUpdateDownload : Form{
|
||||
private const double BytesToMB = 1024.0*1024.0;
|
||||
private const double BytesToKB = 1024.0;
|
||||
|
||||
public string InstallerPath{
|
||||
get{
|
||||
@@ -69,7 +68,7 @@ namespace TweetDck.Updates{
|
||||
progressDownload.SetValueInstant(1000);
|
||||
}
|
||||
|
||||
labelStatus.Text = (e.BytesReceived/BytesToMB).ToString("0.0", CultureInfo.CurrentCulture)+" MB";
|
||||
labelStatus.Text = (long)(e.BytesReceived/BytesToKB)+" kB";
|
||||
}
|
||||
else{
|
||||
if (progressDownload.Style != ProgressBarStyle.Continuous){
|
||||
@@ -77,7 +76,7 @@ namespace TweetDck.Updates{
|
||||
}
|
||||
|
||||
progressDownload.SetValueInstant(e.ProgressPercentage*10);
|
||||
labelStatus.Text = (e.BytesReceived/BytesToMB).ToString("0.0", CultureInfo.CurrentCulture)+" / "+(e.TotalBytesToReceive/BytesToMB).ToString("0.0", CultureInfo.CurrentCulture)+" MB";
|
||||
labelStatus.Text = (long)(e.BytesReceived/BytesToKB)+" / "+(long)(e.TotalBytesToReceive/BytesToKB)+" kB";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@@ -5,14 +5,28 @@ using TweetDck.Core;
|
||||
using TweetDck.Core.Controls;
|
||||
using TweetDck.Core.Utils;
|
||||
using TweetDck.Resources;
|
||||
using TweetDck.Updates.Events;
|
||||
|
||||
namespace TweetDck.Updates{
|
||||
class UpdateHandler{
|
||||
private static bool IsSystemSupported{
|
||||
get{
|
||||
return true; // Environment.OSVersion.Version >= new Version("6.1"); // 6.1 NT version = Windows 7
|
||||
}
|
||||
}
|
||||
|
||||
public UpdaterSettings Settings{
|
||||
get{
|
||||
return settings;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly ChromiumWebBrowser browser;
|
||||
private readonly FormBrowser form;
|
||||
private readonly UpdaterSettings settings;
|
||||
|
||||
public event EventHandler<UpdateAcceptedEventArgs> UpdateAccepted;
|
||||
public event EventHandler<UpdateDismissedEventArgs> UpdateDismissed;
|
||||
public event EventHandler<UpdateCheckEventArgs> CheckFinished;
|
||||
|
||||
private int lastEventId;
|
||||
@@ -23,75 +37,67 @@ namespace TweetDck.Updates{
|
||||
this.settings = settings;
|
||||
|
||||
browser.FrameLoadEnd += browser_FrameLoadEnd;
|
||||
browser.RegisterJsObject("$TDU", new Bridge(this));
|
||||
browser.RegisterAsyncJsObject("$TDU", new Bridge(this));
|
||||
}
|
||||
|
||||
private void browser_FrameLoadEnd(object sender, FrameLoadEndEventArgs e){
|
||||
if (e.Frame.IsMain && BrowserUtils.IsTweetDeckWebsite(e.Frame)){
|
||||
ScriptLoader.ExecuteFile(e.Frame, "update.js");
|
||||
Check(false);
|
||||
}
|
||||
}
|
||||
|
||||
public int Check(bool force){
|
||||
browser.ExecuteScriptAsync("TDUF_runUpdateCheck", force, ++lastEventId);
|
||||
return lastEventId;
|
||||
if (IsSystemSupported){
|
||||
if (Program.UserConfig.EnableUpdateCheck || force){
|
||||
string dismissedUpdate = force || settings.DismissedUpdate == null ? string.Empty : settings.DismissedUpdate;
|
||||
|
||||
browser.ExecuteScriptAsync("TDUF_runUpdateCheck", ++lastEventId, Program.VersionTag, dismissedUpdate, settings.AllowPreReleases);
|
||||
return lastEventId;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
else if (settings.DismissedUpdate != "unsupported"){
|
||||
browser.ExecuteScriptAsync("TDUF_displayNotification", "unsupported");
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private void TriggerUpdateAcceptedEvent(UpdateAcceptedEventArgs args){
|
||||
if (UpdateAccepted != null){
|
||||
form.InvokeSafe(() => UpdateAccepted(this, args));
|
||||
form.InvokeAsyncSafe(() => UpdateAccepted(this, args));
|
||||
}
|
||||
}
|
||||
|
||||
private void TriggerUpdateDismissedEvent(UpdateDismissedEventArgs args){
|
||||
form.InvokeAsyncSafe(() => {
|
||||
settings.DismissedUpdate = args.VersionTag;
|
||||
|
||||
if (UpdateDismissed != null){
|
||||
UpdateDismissed(this, args);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void TriggerCheckFinishedEvent(UpdateCheckEventArgs args){
|
||||
if (CheckFinished != null){
|
||||
form.InvokeSafe(() => CheckFinished(this, args));
|
||||
form.InvokeAsyncSafe(() => CheckFinished(this, args));
|
||||
}
|
||||
}
|
||||
|
||||
public class Bridge{
|
||||
public string BrandName{
|
||||
get{
|
||||
return Program.BrandName;
|
||||
}
|
||||
}
|
||||
|
||||
public string VersionTag{
|
||||
get{
|
||||
return Program.VersionTag;
|
||||
}
|
||||
}
|
||||
|
||||
public bool UpdateCheckEnabled{
|
||||
get{
|
||||
return Program.UserConfig.EnableUpdateCheck;
|
||||
}
|
||||
}
|
||||
|
||||
public string DismissedVersionTag{
|
||||
get{
|
||||
return Program.UserConfig.DismissedUpdate ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public bool AllowPreReleases{
|
||||
get{
|
||||
return owner.settings.AllowPreReleases;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsSystemSupported{
|
||||
get{
|
||||
return Environment.OSVersion.Version >= new Version("6.1"); // 6.1 NT version = Windows 7
|
||||
}
|
||||
}
|
||||
|
||||
private readonly UpdateHandler owner;
|
||||
|
||||
public Bridge(UpdateHandler owner){
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
public void TriggerUpdateCheck(){
|
||||
owner.Check(false);
|
||||
}
|
||||
|
||||
public void OnUpdateCheckFinished(int eventId, bool isUpdateAvailable, string latestVersion){
|
||||
owner.TriggerCheckFinishedEvent(new UpdateCheckEventArgs(eventId, isUpdateAvailable, latestVersion));
|
||||
}
|
||||
@@ -101,10 +107,7 @@ namespace TweetDck.Updates{
|
||||
}
|
||||
|
||||
public void OnUpdateDismissed(string versionTag){
|
||||
owner.form.InvokeSafe(() => {
|
||||
Program.UserConfig.DismissedUpdate = versionTag;
|
||||
Program.UserConfig.Save();
|
||||
});
|
||||
owner.TriggerUpdateDismissedEvent(new UpdateDismissedEventArgs(versionTag));
|
||||
}
|
||||
|
||||
public void OpenBrowser(string url){
|
||||
|
@@ -1,5 +1,6 @@
|
||||
namespace TweetDck.Updates{
|
||||
class UpdaterSettings{
|
||||
public bool AllowPreReleases { get; set; }
|
||||
public string DismissedUpdate { get; set; }
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,5 @@
|
||||
del "bin\x86\Release\*.xml"
|
||||
del "bin\x86\Release\*.pdb"
|
||||
del "bin\x86\Release\devtools_resources.pak"
|
||||
del "bin\x86\Release\d3dcompiler_43.dll"
|
||||
del "bin\x86\Release\widevinecdmadapter.dll"
|
||||
|
@@ -39,7 +39,7 @@ Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{
|
||||
|
||||
[Files]
|
||||
Source: "..\bin\x86\Release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "..\bin\x86\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Excludes: "*.xml,devtools_resources.pak,d3dcompiler_43.dll,widevinecdmadapter.dll,debug.js"
|
||||
Source: "..\bin\x86\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Excludes: "*.xml,*.pdb,devtools_resources.pak,d3dcompiler_43.dll,widevinecdmadapter.dll,debug.js"
|
||||
|
||||
[Icons]
|
||||
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Check: TDIsUninstallable
|
||||
|
@@ -10,14 +10,14 @@
|
||||
|
||||
[Setup]
|
||||
AppId={{8C25A716-7E11-4AAD-9992-8B5D0C78AE06}
|
||||
AppName={#MyAppName}
|
||||
AppName={#MyAppName} Portable
|
||||
AppVersion={#MyAppVersion}
|
||||
AppVerName={#MyAppName} {#MyAppVersion}
|
||||
AppPublisher={#MyAppPublisher}
|
||||
AppPublisherURL={#MyAppURL}
|
||||
AppSupportURL={#MyAppURL}
|
||||
AppUpdatesURL={#MyAppURL}
|
||||
DefaultDirName={pf}\{#MyAppName}
|
||||
DefaultDirName={sd}\{#MyAppName}
|
||||
DefaultGroupName={#MyAppName}
|
||||
OutputBaseFilename={#MyAppName}.Portable
|
||||
VersionInfoVersion={#MyAppVersion}
|
||||
@@ -36,7 +36,7 @@ Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
|
||||
[Files]
|
||||
Source: "..\bin\x86\Release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "..\bin\x86\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Excludes: "*.xml,devtools_resources.pak,d3dcompiler_43.dll,widevinecdmadapter.dll,debug.js"
|
||||
Source: "..\bin\x86\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Excludes: "*.xml,*.pdb,devtools_resources.pak,d3dcompiler_43.dll,widevinecdmadapter.dll,debug.js"
|
||||
|
||||
[Run]
|
||||
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall shellexec
|
||||
|
@@ -8,11 +8,11 @@
|
||||
|
||||
#define MyAppID "8C25A716-7E11-4AAD-9992-8B5D0C78AE06"
|
||||
#define MyAppVersion GetFileVersion("..\bin\x86\Release\TweetDuck.exe")
|
||||
#define CefVersion "3.2785.1486.0"
|
||||
#define CefVersion "3.2883.1552.0"
|
||||
|
||||
[Setup]
|
||||
AppId={{{#MyAppID}}
|
||||
AppName={#MyAppName}
|
||||
AppName={#MyAppName} Update
|
||||
AppVersion={#MyAppVersion}
|
||||
AppVerName={#MyAppName} {#MyAppVersion}
|
||||
AppPublisher={#MyAppPublisher}
|
||||
@@ -41,7 +41,7 @@ Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
|
||||
[Files]
|
||||
Source: "..\bin\x86\Release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "..\bin\x86\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Excludes: "*.xml,*.dll,*.pak,*.bin,*.dat,debug.js"
|
||||
Source: "..\bin\x86\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Excludes: "*.xml,*.pdb,*.dll,*.pak,*.bin,*.dat,debug.js"
|
||||
|
||||
[Icons]
|
||||
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Check: TDIsUninstallable
|
||||
|
143
tests/Core/Settings/TestCombinedFileStream.cs
Normal file
143
tests/Core/Settings/TestCombinedFileStream.cs
Normal file
@@ -0,0 +1,143 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System;
|
||||
using System.IO;
|
||||
using TweetDck.Core.Other.Settings.Export;
|
||||
|
||||
namespace UnitTests.Core.Settings{
|
||||
[TestClass]
|
||||
public class TestCombinedFileStream{
|
||||
[TestMethod]
|
||||
public void TestNoFiles(){
|
||||
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.WriteFile("cfs_empty"))){
|
||||
cfs.Flush();
|
||||
}
|
||||
|
||||
Assert.IsTrue(File.Exists("cfs_empty"));
|
||||
|
||||
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.ReadFile("cfs_empty"))){
|
||||
Assert.IsNull(cfs.ReadFile());
|
||||
}
|
||||
|
||||
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.ReadFile("cfs_empty"))){
|
||||
Assert.IsNull(cfs.SkipFile());
|
||||
}
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestEmptyFiles(){
|
||||
TestUtils.WriteText("cfs_input_empty_1", string.Empty);
|
||||
TestUtils.WriteText("cfs_input_empty_2", string.Empty);
|
||||
|
||||
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.WriteFile("cfs_blank_files"))){
|
||||
cfs.WriteFile("id1", "cfs_input_empty_1");
|
||||
cfs.WriteFile("id2", "cfs_input_empty_2");
|
||||
cfs.WriteFile("id2_clone", "cfs_input_empty_2");
|
||||
cfs.Flush();
|
||||
}
|
||||
|
||||
Assert.IsTrue(File.Exists("cfs_blank_files"));
|
||||
|
||||
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.ReadFile("cfs_blank_files"))){
|
||||
CombinedFileStream.Entry entry1 = cfs.ReadFile();
|
||||
string entry2key = cfs.SkipFile();
|
||||
CombinedFileStream.Entry entry3 = cfs.ReadFile();
|
||||
|
||||
Assert.IsNull(cfs.ReadFile());
|
||||
Assert.IsNull(cfs.SkipFile());
|
||||
|
||||
Assert.AreEqual("id1", entry1.KeyName);
|
||||
Assert.AreEqual("id1", entry1.Identifier);
|
||||
CollectionAssert.AreEqual(new string[0], entry1.KeyValue);
|
||||
|
||||
Assert.AreEqual("id2", entry2key);
|
||||
|
||||
Assert.AreEqual("id2_clone", entry3.KeyName);
|
||||
Assert.AreEqual("id2_clone", entry3.Identifier);
|
||||
CollectionAssert.AreEqual(new string[0], entry3.KeyValue);
|
||||
|
||||
entry1.WriteToFile("cfs_blank_file_1");
|
||||
entry3.WriteToFile("cfs_blank_file_2");
|
||||
TestUtils.DeleteFileOnExit("cfs_blank_file_1");
|
||||
TestUtils.DeleteFileOnExit("cfs_blank_file_2");
|
||||
}
|
||||
|
||||
Assert.IsTrue(File.Exists("cfs_blank_file_1"));
|
||||
Assert.IsTrue(File.Exists("cfs_blank_file_2"));
|
||||
Assert.AreEqual(string.Empty, TestUtils.ReadText("cfs_blank_file_1"));
|
||||
Assert.AreEqual(string.Empty, TestUtils.ReadText("cfs_blank_file_2"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestTextFilesAndComplexKeys(){
|
||||
TestUtils.WriteText("cfs_input_text_1", "Hello World!"+Environment.NewLine);
|
||||
|
||||
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.WriteFile("cfs_text_files"))){
|
||||
cfs.WriteFile(new string[]{ "key1", "a", "bb", "ccc", "dddd" }, "cfs_input_text_1");
|
||||
cfs.WriteFile(new string[]{ "key2", "a", "bb", "ccc", "dddd" }, "cfs_input_text_1");
|
||||
cfs.Flush();
|
||||
}
|
||||
|
||||
Assert.IsTrue(File.Exists("cfs_text_files"));
|
||||
|
||||
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.ReadFile("cfs_text_files"))){
|
||||
CombinedFileStream.Entry entry = cfs.ReadFile();
|
||||
|
||||
Assert.AreEqual("key2", cfs.SkipFile());
|
||||
Assert.IsNull(cfs.ReadFile());
|
||||
Assert.IsNull(cfs.SkipFile());
|
||||
|
||||
Assert.AreEqual("key1|a|bb|ccc|dddd", entry.Identifier);
|
||||
Assert.AreEqual("key1", entry.KeyName);
|
||||
CollectionAssert.AreEqual(new string[]{ "a", "bb", "ccc", "dddd" }, entry.KeyValue);
|
||||
|
||||
entry.WriteToFile("cfs_text_file_1");
|
||||
TestUtils.DeleteFileOnExit("cfs_text_file_1");
|
||||
}
|
||||
|
||||
Assert.IsTrue(File.Exists("cfs_text_file_1"));
|
||||
Assert.AreEqual("Hello World!"+Environment.NewLine, TestUtils.ReadText("cfs_text_file_1"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestEntryWriteWithDirectory(){
|
||||
if (Directory.Exists("cfs_directory")){
|
||||
Directory.Delete("cfs_directory", true);
|
||||
}
|
||||
|
||||
TestUtils.WriteText("cfs_input_dir_1", "test");
|
||||
|
||||
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.WriteFile("cfs_dir_test"))){
|
||||
cfs.WriteFile("key1", "cfs_input_dir_1");
|
||||
cfs.WriteFile("key2", "cfs_input_dir_1");
|
||||
cfs.WriteFile("key3", "cfs_input_dir_1");
|
||||
cfs.WriteFile("key4", "cfs_input_dir_1");
|
||||
cfs.Flush();
|
||||
}
|
||||
|
||||
Assert.IsTrue(File.Exists("cfs_dir_test"));
|
||||
|
||||
using(CombinedFileStream cfs = new CombinedFileStream(TestUtils.ReadFile("cfs_dir_test"))){
|
||||
try{
|
||||
cfs.ReadFile().WriteToFile("cfs_directory/cfs_dir_test_file", false);
|
||||
Assert.Fail("WriteToFile did not trigger an exception.");
|
||||
}catch(DirectoryNotFoundException){}
|
||||
|
||||
cfs.ReadFile().WriteToFile("cfs_directory/cfs_dir_test_file", true);
|
||||
cfs.ReadFile().WriteToFile("cfs_dir_test_file", true);
|
||||
cfs.ReadFile().WriteToFile("cfs_dir_test_file.txt", true);
|
||||
TestUtils.DeleteFileOnExit("cfs_dir_test_file");
|
||||
TestUtils.DeleteFileOnExit("cfs_dir_test_file.txt");
|
||||
}
|
||||
|
||||
Assert.IsTrue(Directory.Exists("cfs_directory"));
|
||||
Assert.IsTrue(File.Exists("cfs_directory/cfs_dir_test_file"));
|
||||
Assert.IsTrue(File.Exists("cfs_dir_test_file"));
|
||||
Assert.IsTrue(File.Exists("cfs_dir_test_file.txt"));
|
||||
Assert.AreEqual("test", TestUtils.ReadText("cfs_directory/cfs_dir_test_file"));
|
||||
Assert.AreEqual("test", TestUtils.ReadText("cfs_dir_test_file"));
|
||||
Assert.AreEqual("test", TestUtils.ReadText("cfs_dir_test_file.txt"));
|
||||
|
||||
Directory.Delete("cfs_directory", true);
|
||||
}
|
||||
}
|
||||
}
|
19
tests/Core/Utils/TestBrowserUtils.cs
Normal file
19
tests/Core/Utils/TestBrowserUtils.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using TweetDck.Core.Utils;
|
||||
|
||||
namespace UnitTests.Core.Utils{
|
||||
[TestClass]
|
||||
public class TestBrowserUtils{
|
||||
[TestMethod]
|
||||
public void TestGetFileNameFromUrl(){
|
||||
Assert.AreEqual("index.html", BrowserUtils.GetFileNameFromUrl("http://test.com/index.html"));
|
||||
Assert.AreEqual("index.html", BrowserUtils.GetFileNameFromUrl("http://test.com/index.html?"));
|
||||
Assert.AreEqual("index.html", BrowserUtils.GetFileNameFromUrl("http://test.com/index.html?param1=abc¶m2=false"));
|
||||
|
||||
Assert.AreEqual("index", BrowserUtils.GetFileNameFromUrl("http://test.com/index"));
|
||||
Assert.AreEqual("index.", BrowserUtils.GetFileNameFromUrl("http://test.com/index."));
|
||||
|
||||
Assert.IsNull(BrowserUtils.GetFileNameFromUrl("http://test.com/"));
|
||||
}
|
||||
}
|
||||
}
|
157
tests/Core/Utils/TestCommandLineArgs.cs
Normal file
157
tests/Core/Utils/TestCommandLineArgs.cs
Normal file
@@ -0,0 +1,157 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using System.Collections.Generic;
|
||||
using TweetDck.Core.Utils;
|
||||
|
||||
namespace UnitTests.Core.Utils{
|
||||
[TestClass]
|
||||
public class TestCommandLineArgs{
|
||||
[TestMethod]
|
||||
public void TestEmpty(){
|
||||
CommandLineArgs args = new CommandLineArgs();
|
||||
|
||||
Assert.AreEqual(0, args.Count);
|
||||
Assert.AreEqual(string.Empty, args.ToString());
|
||||
|
||||
Assert.IsFalse(args.HasFlag("x"));
|
||||
Assert.IsFalse(args.HasValue("x"));
|
||||
Assert.AreEqual("default", args.GetValue("x", "default"));
|
||||
|
||||
args.RemoveFlag("x");
|
||||
args.RemoveValue("x");
|
||||
|
||||
var dict = new Dictionary<string, string>();
|
||||
args.ToDictionary(dict);
|
||||
Assert.AreEqual(0, dict.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestFlags(){
|
||||
CommandLineArgs args = new CommandLineArgs();
|
||||
|
||||
args.AddFlag("my_test_flag_1");
|
||||
args.AddFlag("my_test_flag_2");
|
||||
args.AddFlag("aAaAa");
|
||||
|
||||
Assert.IsFalse(args.HasValue("aAaAa"));
|
||||
|
||||
Assert.AreEqual(3, args.Count);
|
||||
Assert.IsTrue(args.HasFlag("my_test_flag_1"));
|
||||
Assert.IsTrue(args.HasFlag("my_test_flag_2"));
|
||||
Assert.IsTrue(args.HasFlag("aaaaa"));
|
||||
Assert.IsTrue(args.HasFlag("AAAAA"));
|
||||
Assert.AreEqual("my_test_flag_1 my_test_flag_2 aaaaa", args.ToString());
|
||||
|
||||
args.RemoveFlag("Aaaaa");
|
||||
|
||||
Assert.AreEqual(2, args.Count);
|
||||
Assert.IsTrue(args.HasFlag("my_test_flag_1"));
|
||||
Assert.IsTrue(args.HasFlag("my_test_flag_2"));
|
||||
Assert.IsFalse(args.HasFlag("aaaaa"));
|
||||
Assert.AreEqual("my_test_flag_1 my_test_flag_2", args.ToString());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestValues(){
|
||||
CommandLineArgs args = new CommandLineArgs();
|
||||
|
||||
args.SetValue("test_value", "My Test Value");
|
||||
args.SetValue("aAaAa", "aaaaa");
|
||||
|
||||
Assert.IsFalse(args.HasFlag("aAaAa"));
|
||||
|
||||
Assert.AreEqual(2, args.Count);
|
||||
Assert.IsTrue(args.HasValue("test_value"));
|
||||
Assert.IsTrue(args.HasValue("aaaaa"));
|
||||
Assert.IsTrue(args.HasValue("AAAAA"));
|
||||
Assert.AreEqual("My Test Value", args.GetValue("test_value", string.Empty));
|
||||
Assert.AreEqual("aaaaa", args.GetValue("aaaaa", string.Empty));
|
||||
Assert.AreEqual("test_value \"My Test Value\" aaaaa \"aaaaa\"", args.ToString());
|
||||
|
||||
args.RemoveValue("Aaaaa");
|
||||
|
||||
Assert.AreEqual(1, args.Count);
|
||||
Assert.IsTrue(args.HasValue("test_value"));
|
||||
Assert.IsFalse(args.HasValue("aaaaa"));
|
||||
Assert.AreEqual("test_value \"My Test Value\"", args.ToString());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestFlagAndValueMix(){
|
||||
CommandLineArgs args = new CommandLineArgs();
|
||||
|
||||
args.AddFlag("my_test_flag_1");
|
||||
args.AddFlag("my_test_flag_2");
|
||||
args.AddFlag("aAaAa");
|
||||
|
||||
args.SetValue("test_value", "My Test Value");
|
||||
args.SetValue("aAaAa", "aaaaa");
|
||||
|
||||
Assert.AreEqual(5, args.Count);
|
||||
Assert.IsTrue(args.HasFlag("aaaaa"));
|
||||
Assert.IsTrue(args.HasValue("aaaaa"));
|
||||
Assert.AreEqual("my_test_flag_1 my_test_flag_2 aaaaa test_value \"My Test Value\" aaaaa \"aaaaa\"", args.ToString());
|
||||
|
||||
var dict = new Dictionary<string, string>();
|
||||
args.ToDictionary(dict); // loses 'aaaaa' flag
|
||||
|
||||
Assert.AreEqual(4, dict.Count);
|
||||
Assert.AreEqual("1", dict["my_test_flag_1"]);
|
||||
Assert.AreEqual("1", dict["my_test_flag_2"]);
|
||||
Assert.AreEqual("My Test Value", dict["test_value"]);
|
||||
Assert.AreEqual("aaaaa", dict["aaaaa"]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestClone(){
|
||||
CommandLineArgs args = new CommandLineArgs();
|
||||
|
||||
args.AddFlag("my_test_flag_1");
|
||||
args.AddFlag("my_test_flag_2");
|
||||
args.AddFlag("aAaAa");
|
||||
|
||||
args.SetValue("test_value", "My Test Value");
|
||||
args.SetValue("aAaAa", "aaaaa");
|
||||
|
||||
CommandLineArgs clone = args.Clone();
|
||||
args.RemoveFlag("aaaaa");
|
||||
args.RemoveValue("aaaaa");
|
||||
clone.RemoveFlag("my_test_flag_1");
|
||||
clone.RemoveFlag("my_test_flag_2");
|
||||
clone.RemoveValue("test_value");
|
||||
|
||||
Assert.AreEqual(3, args.Count);
|
||||
Assert.AreEqual(2, clone.Count);
|
||||
|
||||
Assert.AreEqual("my_test_flag_1 my_test_flag_2 test_value \"My Test Value\"", args.ToString());
|
||||
Assert.AreEqual("aaaaa aaaaa \"aaaaa\"", clone.ToString());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestEmptyStringArray(){
|
||||
CommandLineArgs args;
|
||||
|
||||
args = CommandLineArgs.FromStringArray('-', new string[0]);
|
||||
Assert.AreEqual(0, args.Count);
|
||||
|
||||
args = CommandLineArgs.FromStringArray('-', new string[]{ "", "+fail", "@nope" });
|
||||
Assert.AreEqual(0, args.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestValidStringArray(){
|
||||
CommandLineArgs args;
|
||||
|
||||
args = CommandLineArgs.FromStringArray('-', new string[]{ "-flag1", "-flag2", "-FLAG3" });
|
||||
Assert.AreEqual(3, args.Count);
|
||||
Assert.IsTrue(args.HasFlag("-flag1"));
|
||||
Assert.IsTrue(args.HasFlag("-flag2"));
|
||||
Assert.IsTrue(args.HasFlag("-flag3"));
|
||||
|
||||
args = CommandLineArgs.FromStringArray('-', new string[]{ "-flag", "-value", "Here is some text!" });
|
||||
Assert.AreEqual(2, args.Count);
|
||||
Assert.IsTrue(args.HasFlag("-flag"));
|
||||
Assert.IsTrue(args.HasValue("-value"));
|
||||
Assert.AreEqual("Here is some text!", args.GetValue("-value", string.Empty));
|
||||
}
|
||||
}
|
||||
}
|
32
tests/Core/Utils/TestCommandLineArgsParser.cs
Normal file
32
tests/Core/Utils/TestCommandLineArgsParser.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using TweetDck.Core.Utils;
|
||||
|
||||
namespace UnitTests.Core.Utils{
|
||||
[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\" --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));
|
||||
}
|
||||
}
|
||||
}
|
201
tests/Core/Utils/TestTwoKeyDictionary.cs
Normal file
201
tests/Core/Utils/TestTwoKeyDictionary.cs
Normal file
@@ -0,0 +1,201 @@
|
||||
using System;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using TweetDck.Core.Utils;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnitTests.Core.Utils{
|
||||
[TestClass]
|
||||
public class TestTwoKeyDictionary{
|
||||
private static TwoKeyDictionary<string, int, string> CreateDict(){
|
||||
TwoKeyDictionary<string, int, string> dict = new TwoKeyDictionary<string, int, string>();
|
||||
|
||||
dict.Add("aaa", 0, "x");
|
||||
dict.Add("aaa", 1, "y");
|
||||
dict.Add("aaa", 2, "z");
|
||||
|
||||
dict.Add("bbb", 0, "test 1");
|
||||
dict.Add("bbb", 10, "test 2");
|
||||
dict.Add("bbb", 20, "test 3");
|
||||
dict.Add("bbb", 30, "test 4");
|
||||
|
||||
dict.Add("ccc", -5, "");
|
||||
dict.Add("", 0, "");
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestAdd(){
|
||||
CreateDict();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentException))]
|
||||
public void TestAddDuplicate(){
|
||||
var dict = new TwoKeyDictionary<string, int, string>();
|
||||
|
||||
dict.Add("aaa", 0, "test");
|
||||
dict.Add("aaa", 0, "oops");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestAccessor(){
|
||||
var dict = CreateDict();
|
||||
|
||||
// get
|
||||
|
||||
Assert.AreEqual("x", dict["aaa", 0]);
|
||||
Assert.AreEqual("y", dict["aaa", 1]);
|
||||
Assert.AreEqual("z", dict["aaa", 2]);
|
||||
|
||||
Assert.AreEqual("test 3", dict["bbb", 20]);
|
||||
|
||||
Assert.AreEqual("", dict["ccc", -5]);
|
||||
Assert.AreEqual("", dict["", 0]);
|
||||
|
||||
// set
|
||||
|
||||
dict["aaa", 0] = "replaced entry";
|
||||
Assert.AreEqual("replaced entry", dict["aaa", 0]);
|
||||
|
||||
dict["aaa", 3] = "new entry";
|
||||
Assert.AreEqual("new entry", dict["aaa", 3]);
|
||||
|
||||
dict["xxxxx", 150] = "new key and entry";
|
||||
Assert.AreEqual("new key and entry", dict["xxxxx", 150]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(KeyNotFoundException))]
|
||||
public void TestAccessorMissingKey1(){
|
||||
var _ = CreateDict()["missing", 0];
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(KeyNotFoundException))]
|
||||
public void TestAccessorMissingKey2(){
|
||||
var _ = CreateDict()["aaa", 3];
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestClear(){
|
||||
var dict = CreateDict();
|
||||
|
||||
Assert.IsTrue(dict.Contains("bbb"));
|
||||
dict.Clear("bbb");
|
||||
Assert.IsTrue(dict.Contains("bbb"));
|
||||
|
||||
Assert.IsTrue(dict.Contains(""));
|
||||
dict.Clear("");
|
||||
Assert.IsTrue(dict.Contains(""));
|
||||
|
||||
Assert.IsTrue(dict.Contains("aaa"));
|
||||
Assert.IsTrue(dict.Contains("ccc"));
|
||||
dict.Clear();
|
||||
Assert.IsFalse(dict.Contains("aaa"));
|
||||
Assert.IsFalse(dict.Contains("ccc"));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(KeyNotFoundException))]
|
||||
public void TestClearMissingKey(){
|
||||
CreateDict().Clear("missing");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestContains(){
|
||||
var dict = CreateDict();
|
||||
|
||||
// positive
|
||||
|
||||
Assert.IsTrue(dict.Contains("aaa"));
|
||||
Assert.IsTrue(dict.Contains("aaa", 0));
|
||||
Assert.IsTrue(dict.Contains("aaa", 1));
|
||||
Assert.IsTrue(dict.Contains("aaa", 2));
|
||||
|
||||
Assert.IsTrue(dict.Contains("ccc"));
|
||||
Assert.IsTrue(dict.Contains("ccc", -5));
|
||||
|
||||
Assert.IsTrue(dict.Contains(""));
|
||||
Assert.IsTrue(dict.Contains("", 0));
|
||||
|
||||
// negative
|
||||
|
||||
Assert.IsFalse(dict.Contains("missing"));
|
||||
Assert.IsFalse(dict.Contains("missing", 999));
|
||||
|
||||
Assert.IsFalse(dict.Contains("aaa", 3));
|
||||
Assert.IsFalse(dict.Contains("", -1));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestCount(){
|
||||
var dict = CreateDict();
|
||||
|
||||
Assert.AreEqual(9, dict.Count());
|
||||
Assert.AreEqual(3, dict.Count("aaa"));
|
||||
Assert.AreEqual(4, dict.Count("bbb"));
|
||||
Assert.AreEqual(1, dict.Count("ccc"));
|
||||
Assert.AreEqual(1, dict.Count(""));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(KeyNotFoundException))]
|
||||
public void TestCountMissingKey(){
|
||||
CreateDict().Count("missing");
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestRemove(){
|
||||
var dict = CreateDict();
|
||||
|
||||
// negative
|
||||
Assert.IsFalse(dict.Remove("missing"));
|
||||
Assert.IsFalse(dict.Remove("aaa", 3));
|
||||
|
||||
// positive
|
||||
|
||||
Assert.IsTrue(dict.Contains("aaa"));
|
||||
Assert.IsTrue(dict.Remove("aaa"));
|
||||
Assert.IsFalse(dict.Contains("aaa"));
|
||||
|
||||
Assert.IsTrue(dict.Contains("bbb", 10));
|
||||
Assert.IsTrue(dict.Remove("bbb", 10));
|
||||
Assert.IsFalse(dict.Contains("bbb", 10));
|
||||
Assert.IsTrue(dict.Contains("bbb"));
|
||||
Assert.IsTrue(dict.Contains("bbb", 20));
|
||||
|
||||
Assert.IsTrue(dict.Remove("bbb", 0));
|
||||
Assert.IsTrue(dict.Remove("bbb", 20));
|
||||
Assert.IsTrue(dict.Remove("bbb", 30));
|
||||
Assert.IsFalse(dict.Contains("bbb"));
|
||||
|
||||
Assert.IsTrue(dict.Contains(""));
|
||||
Assert.IsTrue(dict.Remove("", 0));
|
||||
Assert.IsFalse(dict.Contains(""));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestTryGetValue(){
|
||||
var dict = CreateDict();
|
||||
string val;
|
||||
|
||||
// positive
|
||||
|
||||
Assert.IsTrue(dict.TryGetValue("bbb", 10, out val));
|
||||
Assert.AreEqual("test 2", val);
|
||||
|
||||
Assert.IsTrue(dict.TryGetValue("ccc", -5, out val));
|
||||
Assert.AreEqual("", val);
|
||||
|
||||
Assert.IsTrue(dict.TryGetValue("", 0, out val));
|
||||
Assert.AreEqual("", val);
|
||||
|
||||
// negative
|
||||
|
||||
Assert.IsFalse(dict.TryGetValue("ccc", -50, out val));
|
||||
Assert.IsFalse(dict.TryGetValue("", 1, out val));
|
||||
Assert.IsFalse(dict.TryGetValue("missing", 0, out val));
|
||||
}
|
||||
}
|
||||
}
|
35
tests/Properties/AssemblyInfo.cs
Normal file
35
tests/Properties/AssemblyInfo.cs
Normal 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("UnitTests")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("UnitTests")]
|
||||
[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("99036b78-aad6-4a76-8bf3-40c77eca2464")]
|
||||
|
||||
// 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")]
|
65
tests/TestUtils.cs
Normal file
65
tests/TestUtils.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Linq;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace UnitTests{
|
||||
public static class TestUtils{
|
||||
private static readonly HashSet<string> CreatedFiles = new HashSet<string>();
|
||||
|
||||
public static void WriteText(string file, string text){
|
||||
DeleteFileOnExit(file);
|
||||
File.WriteAllText(file, text, Encoding.UTF8);
|
||||
}
|
||||
|
||||
public static void WriteLines(string file, IEnumerable<string> lines){
|
||||
DeleteFileOnExit(file);
|
||||
File.WriteAllLines(file, lines, Encoding.UTF8);
|
||||
}
|
||||
|
||||
public static FileStream WriteFile(string file){
|
||||
DeleteFileOnExit(file);
|
||||
return new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.None);
|
||||
}
|
||||
|
||||
public static string ReadText(string file){
|
||||
try{
|
||||
return File.ReadAllText(file, Encoding.UTF8);
|
||||
}catch(Exception){
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<string> ReadLines(string file){
|
||||
try{
|
||||
return File.ReadLines(file, Encoding.UTF8);
|
||||
}catch(Exception){
|
||||
return Enumerable.Empty<string>();
|
||||
}
|
||||
}
|
||||
|
||||
public static FileStream ReadFile(string file){
|
||||
return new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None);
|
||||
}
|
||||
|
||||
public static void DeleteFileOnExit(string file){
|
||||
CreatedFiles.Add(file);
|
||||
}
|
||||
|
||||
[TestClass]
|
||||
public static class Cleanup{
|
||||
[AssemblyCleanup]
|
||||
public static void DeleteFilesOnExit(){
|
||||
foreach(string file in CreatedFiles){
|
||||
try{
|
||||
File.Delete(file);
|
||||
}catch(Exception){
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
91
tests/UnitTests.csproj
Normal file
91
tests/UnitTests.csproj
Normal file
@@ -0,0 +1,91 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{A958FA7A-4A2C-42A7-BFA0-159343483F4E}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>UnitTests</RootNamespace>
|
||||
<AssemblyName>UnitTests</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
<ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath>
|
||||
<IsCodedUITest>False</IsCodedUITest>
|
||||
<TestProjectType>UnitTest</TestProjectType>
|
||||
<TargetFrameworkProfile />
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<OutputPath>bin\x86\Debug\</OutputPath>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<OutputPath>bin\x86\Release\</OutputPath>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core">
|
||||
<RequiredTargetFramework>3.5</RequiredTargetFramework>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<Choose>
|
||||
<When Condition="('$(VisualStudioVersion)' == '10.0' or '$(VisualStudioVersion)' == '') and '$(TargetFrameworkVersion)' == 'v3.5'">
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
|
||||
</ItemGroup>
|
||||
</When>
|
||||
<Otherwise>
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework" />
|
||||
</ItemGroup>
|
||||
</Otherwise>
|
||||
</Choose>
|
||||
<ItemGroup>
|
||||
<Compile Include="Core\Settings\TestCombinedFileStream.cs" />
|
||||
<Compile Include="Core\Utils\TestBrowserUtils.cs" />
|
||||
<Compile Include="Core\Utils\TestCommandLineArgs.cs" />
|
||||
<Compile Include="Core\Utils\TestCommandLineArgsParser.cs" />
|
||||
<Compile Include="Core\Utils\TestTwoKeyDictionary.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="TestUtils.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\TweetDck.csproj">
|
||||
<Project>{2389a7cd-e0d3-4706-8294-092929a33a2d}</Project>
|
||||
<Name>TweetDck</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Choose>
|
||||
<When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'">
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.VisualStudio.QualityTools.CodedUITestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.VisualStudio.TestTools.UITest.Common, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.VisualStudio.TestTools.UITest.Extension, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.VisualStudio.TestTools.UITesting, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
</When>
|
||||
</Choose>
|
||||
<Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
Reference in New Issue
Block a user