1
0
Fork 0

Compare commits

...

11 Commits

78 changed files with 572 additions and 24 deletions

View File

@ -12,6 +12,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetImpl.CefSharp", "windo
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetLib.WinForms.Legacy", "windows\TweetLib.WinForms.Legacy\TweetLib.WinForms.Legacy.csproj", "{B54E732A-4090-4DAA-9ABD-311368C17B68}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetLib.Api", "lib\TweetLib.Api\TweetLib.Api.csproj", "{85596C10-F76E-4619-9CC6-6C1593880F83}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetLib.Communication", "lib\TweetLib.Communication\TweetLib.Communication.csproj", "{72473763-4B9D-4FB6-A923-9364B2680F06}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetLib.Core", "lib\TweetLib.Core\TweetLib.Core.csproj", "{93BA3CB4-A812-4949-B07D-8D393FB38937}"
@ -54,6 +56,10 @@ Global
{B54E732A-4090-4DAA-9ABD-311368C17B68}.Debug|x86.Build.0 = Debug|x86
{B54E732A-4090-4DAA-9ABD-311368C17B68}.Release|x86.ActiveCfg = Release|x86
{B54E732A-4090-4DAA-9ABD-311368C17B68}.Release|x86.Build.0 = Release|x86
{85596C10-F76E-4619-9CC6-6C1593880F83}.Debug|x86.ActiveCfg = Debug|x86
{85596C10-F76E-4619-9CC6-6C1593880F83}.Debug|x86.Build.0 = Debug|x86
{85596C10-F76E-4619-9CC6-6C1593880F83}.Release|x86.ActiveCfg = Release|x86
{85596C10-F76E-4619-9CC6-6C1593880F83}.Release|x86.Build.0 = Release|x86
{72473763-4B9D-4FB6-A923-9364B2680F06}.Debug|x86.ActiveCfg = Debug|x86
{72473763-4B9D-4FB6-A923-9364B2680F06}.Debug|x86.Build.0 = Debug|x86
{72473763-4B9D-4FB6-A923-9364B2680F06}.Release|x86.ActiveCfg = Release|x86

View File

@ -6,6 +6,6 @@ using TweetDuck;
namespace TweetDuck {
internal static class Version {
public const string Tag = "1.25.1";
public const string Tag = "1.25.4";
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
bld/Redist/ucrtbase.dll Normal file

Binary file not shown.

View File

@ -77,10 +77,8 @@ Type: files; Name: "{app}\cef_200_percent.pak"
Type: files; Name: "{app}\cef_extensions.pak"
Type: files; Name: "{app}\devtools_resources.pak"
Type: files; Name: "{app}\natives_blob.bin"
Type: files; Name: "{app}\api-ms-win-*.dll"
Type: files; Name: "{app}\dbgshim.dll"
Type: files; Name: "{app}\mscordaccore_x86_x86_6.*.dll"
Type: files; Name: "{app}\ucrtbase.dll"
Type: filesandordirs; Name: "{app}\guide"
Type: filesandordirs; Name: "{app}\plugins\official"
Type: filesandordirs; Name: "{app}\resources"

View File

@ -0,0 +1,37 @@
namespace TweetLib.Api.Data {
public readonly struct NamespacedResource {
public Resource Namespace { get; }
public Resource Path { get; }
public NamespacedResource(Resource ns, Resource path) {
Namespace = ns;
Path = path;
}
private bool Equals(NamespacedResource other) {
return Namespace.Equals(other.Namespace) && Path.Equals(other.Path);
}
public override bool Equals(object? obj) {
return obj is NamespacedResource other && Equals(other);
}
public static bool operator ==(NamespacedResource left, NamespacedResource right) {
return left.Equals(right);
}
public static bool operator !=(NamespacedResource left, NamespacedResource right) {
return !left.Equals(right);
}
public override int GetHashCode() {
unchecked {
return (Namespace.GetHashCode() * 397) ^ Path.GetHashCode();
}
}
public override string ToString() {
return $"{Namespace}:{Path}";
}
}
}

View File

@ -0,0 +1,28 @@
namespace TweetLib.Api.Data.Notification {
/// <summary>
/// Allows extensions to decide which screen to use for notifications.
///
/// Every registered provider becomes available in the Options dialog and has to be explicitly selected by the user. Only one provider
/// can be active at any given time.
/// </summary>
public interface IDesktopNotificationScreenProvider {
/// <summary>
/// A unique identifier of this provider. Only needs to be unique within the scope of this plugin.
/// </summary>
Resource Id { get; }
/// <summary>
/// Text displayed in the user interface.
/// </summary>
string DisplayName { get; }
/// <summary>
/// Returns a screen that will be used to display the next desktop notification.
///
/// If the return value is <c>null</c> or a screen that is not present in <see cref="IScreenLayout.AllScreens" />, desktop
/// notifications will be temporarily paused and this method will be called again after an unspecified amount of time (but
/// not sooner than 1 second since the last call).
/// </summary>
IScreen? PickScreen(IScreenLayout layout);
}
}

View File

@ -0,0 +1,7 @@
namespace TweetLib.Api.Data.Notification {
public interface IScreen {
ScreenBounds Bounds { get; }
string Name { get; }
bool IsPrimary { get; }
}
}

View File

@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace TweetLib.Api.Data.Notification {
public interface IScreenLayout {
IScreen PrimaryScreen { get; }
IScreen TweetDuckScreen { get; }
List<IScreen> AllScreens { get; }
}
}

View File

@ -0,0 +1,18 @@
namespace TweetLib.Api.Data.Notification {
public readonly struct ScreenBounds {
public int X1 { get; }
public int Y1 { get; }
public int Width { get; }
public int Height { get; }
public int X2 => X1 + Width;
public int Y2 => Y1 + Height;
public ScreenBounds(int x1, int y1, int width, int height) {
X1 = x1;
Y1 = y1;
Width = width;
Height = height;
}
}
}

View File

@ -0,0 +1,43 @@
using System;
using System.Text.RegularExpressions;
namespace TweetLib.Api.Data {
public readonly struct Resource {
private const string ValidCharacterPattern = "^[a-z0-9_]+$";
private static readonly Regex ValidCharacterRegex = new Regex(ValidCharacterPattern, RegexOptions.Compiled);
public string Name { get; }
public Resource(string name) {
if (!ValidCharacterRegex.IsMatch(name)) {
throw new ArgumentException("Resource name must match the regex: " + ValidCharacterPattern);
}
Name = name;
}
private bool Equals(Resource other) {
return Name == other.Name;
}
public override bool Equals(object? obj) {
return obj is Resource other && Equals(other);
}
public static bool operator ==(Resource left, Resource right) {
return left.Equals(right);
}
public static bool operator !=(Resource left, Resource right) {
return !left.Equals(right);
}
public override int GetHashCode() {
return Name.GetHashCode();
}
public override string ToString() {
return Name;
}
}
}

View File

@ -0,0 +1,5 @@
namespace TweetLib.Api {
public interface ITweetDuckApi {
T? FindService<T>() where T : class, ITweetDuckService;
}
}

View File

@ -0,0 +1,3 @@
namespace TweetLib.Api {
public interface ITweetDuckService {}
}

View File

@ -0,0 +1,7 @@
using TweetLib.Api.Data.Notification;
namespace TweetLib.Api.Service {
public interface INotificationService : ITweetDuckService {
void RegisterDesktopNotificationScreenProvider(IDesktopNotificationScreenProvider provider);
}
}

View File

@ -0,0 +1,15 @@
using TweetLib.Api.Data;
namespace TweetLib.Api {
public abstract class TweetDuckExtension {
/// <summary>
/// Unique identifier of the extension.
/// </summary>
public abstract Resource Id { get; }
/// <summary>
/// Called when the extension is loaded on startup, or enabled at runtime.
/// </summary>
public abstract void Enable(ITweetDuckApi api);
}
}

View File

@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Configurations>Debug;Release</Configurations>
<Platforms>x86;x64</Platforms>
<LangVersion>10</LangVersion>
<Nullable>enable</Nullable>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\..\Version.cs" Link="Version.cs" />
</ItemGroup>
</Project>

View File

@ -5,6 +5,8 @@ using TweetLib.Browser.Request;
using TweetLib.Core.Application;
using TweetLib.Core.Features;
using TweetLib.Core.Features.Plugins;
using TweetLib.Core.Resources;
using TweetLib.Core.Systems.Api;
using TweetLib.Core.Systems.Configuration;
using TweetLib.Core.Systems.Logging;
using TweetLib.Utils.Static;
@ -21,8 +23,11 @@ namespace TweetLib.Core {
internal static readonly string PluginPath = Path.Combine(ProgramPath, "plugins");
internal static readonly string GuidePath = Path.Combine(ProgramPath, "guide");
public static readonly string StoragePath = IsPortable ? Path.Combine(ProgramPath, "portable", "storage") : GetDataFolder();
public static readonly string LogoPath = Path.Combine(ResourcesPath, "images/logo.png");
public static readonly string ExtensionPath = Path.Combine(ProgramPath, "extensions");
public static readonly string StoragePath = IsPortable ? Path.Combine(ProgramPath, "portable", "storage") : GetDataFolder();
public static readonly string LogoPath = Path.Combine(ResourcesPath, "images/logo.png");
public static ApiImplementation Api { get; } = new ();
public static Logger Logger { get; } = new (Path.Combine(StoragePath, "TD_Log.txt"), Setup.IsDebugLogging);
public static ConfigManager ConfigManager { get; } = Setup.CreateConfigManager(StoragePath);

View File

@ -0,0 +1,41 @@
using System;
using System.IO;
using System.Reflection;
using TweetLib.Api;
using TweetLib.Core.Systems.Api;
namespace TweetLib.Core.Features.Extensions {
public static class ExtensionLoader {
public static void LoadAllInFolder(string extensionFolder) {
if (!Directory.Exists(extensionFolder)) {
return;
}
try {
foreach (string file in Directory.EnumerateFiles(extensionFolder, "*.dll", SearchOption.TopDirectoryOnly)) {
try {
Assembly assembly = Assembly.LoadFile(file);
foreach (Type type in assembly.GetTypes()) {
if (typeof(TweetDuckExtension).IsAssignableFrom(type) && Activator.CreateInstance(type) is TweetDuckExtension extension) {
EnableExtension(extension);
}
}
} catch (Exception e) {
App.ErrorHandler.HandleException("Extension Error", "Could not load extension: " + Path.GetFileNameWithoutExtension(file), true, e);
}
}
} catch (DirectoryNotFoundException) {
// ignore
} catch (Exception e) {
App.ErrorHandler.HandleException("Extension Error", "Could not load extensions.", true, e);
}
}
private static void EnableExtension(TweetDuckExtension extension) {
ApiImplementation apiImplementation = App.Api;
apiImplementation.CurrentExtension = extension;
extension.Enable(apiImplementation);
apiImplementation.CurrentExtension = null;
}
}
}

View File

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using TweetLib.Api;
namespace TweetLib.Core.Systems.Api {
public class ApiImplementation : ITweetDuckApi {
public TweetDuckExtension? CurrentExtension { get; internal set; }
private readonly Dictionary<Type, ITweetDuckService> services = new Dictionary<Type, ITweetDuckService>();
internal ApiImplementation() {}
public void RegisterService<T>(T service) where T : class, ITweetDuckService {
if (!typeof(T).IsInterface) {
throw new ArgumentException("Api service implementation must be registered with its interface type.");
}
services.Add(typeof(T), service);
}
public T? FindService<T>() where T : class, ITweetDuckService {
return services.TryGetValue(typeof(T), out ITweetDuckService? service) ? service as T : null;
}
}
}

View File

@ -10,7 +10,7 @@ using TweetLib.Utils.Static;
namespace TweetLib.Core.Systems.Configuration {
public abstract class ConfigManager {
protected static TypeConverterRegistry ConverterRegistry { get; } = new ();
public static TypeConverterRegistry ConverterRegistry { get; } = new ();
static ConfigManager() {
ConverterRegistry.Register(typeof(WindowState), new BasicTypeConverter<WindowState> {

View File

@ -14,6 +14,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\TweetLib.Api\TweetLib.Api.csproj" />
<ProjectReference Include="..\TweetLib.Browser\TweetLib.Browser.csproj" />
<ProjectReference Include="..\TweetLib.Utils\TweetLib.Utils.csproj" />
</ItemGroup>

View File

@ -67,6 +67,7 @@ import setup_tweet_context_menu from "./tweetdeck/setup_tweet_context_menu.js";
import setup_tweetduck_account_bamboozle from "./tweetdeck/setup_tweetduck_account_bamboozle.js";
import setup_video_player from "./tweetdeck/setup_video_player.js";
import skip_pre_login_page from "./tweetdeck/skip_pre_login_page.js";
import tweetdeck_preview_warning from "./tweetdeck/tweetdeck_preview_warning.js";
import update from "./update/update.js";
const globalFunctions = [

View File

@ -133,6 +133,14 @@ button {
bottom: 192px !important;
}
/***********************/
/* Hide Preview button */
/***********************/
.js-gryphon-beta-btn {
display: none !important;
}
/*************************************/
/* Tweak collapsed left panel layout */
/*************************************/

View File

@ -0,0 +1,7 @@
import { $TD } from "../api/bridge.js";
export default function() {
if (!("TD" in window)) {
$TD.alert("warning", "Some TweetDuck features failed to load. This might happen if your Twitter account is enrolled into the TweetDeck Preview, which TweetDuck does not support. Try opting out of the TweetDeck Preview to restore TweetDuck's functionality.");
}
}

View File

@ -237,7 +237,7 @@ namespace TweetDuck.Video {
int maxWidth = Math.Min(DpiScaled(media.imageSourceWidth), ownerWidth * 3 / 4);
int maxHeight = Math.Min(DpiScaled(media.imageSourceHeight), ownerHeight * 3 / 4);
bool isCursorInside = ClientRectangle.Contains(PointToClient(Cursor.Position));
bool isCursorInside = ClientRectangle.Contains(PointToClient(Cursor.Position)) && Handle == NativeMethods.GetFormHandleAt(Cursor.Position);
Size newSize = new Size(Math.Max(minWidth + 2, maxWidth), Math.Max(minHeight + 2, maxHeight));
Point newLocation = new Point(ownerLeft + (ownerWidth - newSize.Width) / 2, ownerTop + (ownerHeight - newSize.Height + SystemInformation.CaptionHeight) / 2);

View File

@ -1,11 +1,13 @@
#nullable enable
using System;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Runtime.InteropServices;
namespace TweetDuck.Video {
[SuppressMessage("ReSharper", "InconsistentNaming")]
static class NativeMethods {
private const int GA_ROOT = 2;
private const int GWL_HWNDPARENT = -8;
[DllImport("user32.dll")]
@ -24,6 +26,12 @@ namespace TweetDuck.Video {
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
private static extern IntPtr WindowFromPoint(Point point);
[DllImport("user32.dll")]
private static extern IntPtr GetAncestor(IntPtr hwnd, int flags);
[DllImport("user32.dll")]
public static extern bool SetProcessDPIAware();
@ -49,5 +57,10 @@ namespace TweetDuck.Video {
* ...so technically, this is following the documentation to the word.
*/
}
public static IntPtr GetFormHandleAt(Point point) {
IntPtr window = WindowFromPoint(point);
return window == IntPtr.Zero ? IntPtr.Zero : GetAncestor(window, GA_ROOT);
}
}
}

View File

@ -0,0 +1,21 @@
using System;
using TweetDuck.Application.Service;
using TweetLib.Api;
using TweetLib.Api.Data;
using TweetLib.Api.Service;
using TweetLib.Core;
namespace TweetDuck.Application {
static class ApiServices {
public static NotificationService Notifications { get; } = new NotificationService();
public static void Register() {
App.Api.RegisterService<INotificationService>(Notifications);
}
internal static NamespacedResource Namespace(Resource path) {
TweetDuckExtension currentExtension = App.Api.CurrentExtension ?? throw new InvalidOperationException("Cannot use API services outside of designated method calls.");
return new NamespacedResource(currentExtension.Id, path);
}
}
}

View File

@ -0,0 +1,33 @@
using System.Collections.Generic;
using TweetLib.Api.Data;
using TweetLib.Api.Data.Notification;
using TweetLib.Api.Service;
namespace TweetDuck.Application.Service {
sealed class NotificationService : INotificationService {
private readonly List<NamespacedProvider> desktopNotificationScreenProviders = new ();
public void RegisterDesktopNotificationScreenProvider(IDesktopNotificationScreenProvider provider) {
desktopNotificationScreenProviders.Add(new NamespacedProvider(ApiServices.Namespace(provider.Id), provider));
}
public List<NamespacedProvider> GetDesktopNotificationScreenProviders() {
return desktopNotificationScreenProviders;
}
public sealed class NamespacedProvider : IDesktopNotificationScreenProvider {
public NamespacedResource NamespacedId { get; }
private readonly IDesktopNotificationScreenProvider provider;
public NamespacedProvider(NamespacedResource id, IDesktopNotificationScreenProvider provider) {
this.NamespacedId = id;
this.provider = provider;
}
public Resource Id => provider.Id;
public string DisplayName => provider.DisplayName;
public IScreen? PickScreen(IScreenLayout layout) => provider.PickScreen(layout);
}
}
}

View File

@ -22,15 +22,7 @@ namespace TweetDuck.Browser.Notification {
protected virtual Point PrimaryLocation {
get {
Screen screen;
if (Config.NotificationDisplay > 0 && Config.NotificationDisplay <= Screen.AllScreens.Length) {
screen = Screen.AllScreens[Config.NotificationDisplay - 1];
}
else {
screen = Screen.FromControl(owner);
}
Screen screen = Config.NotificationDisplay.PickScreen(owner);
int edgeDist = Config.NotificationEdgeDistance;
switch (Config.NotificationPosition) {

View File

@ -0,0 +1,173 @@
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using TweetDuck.Application;
using TweetDuck.Application.Service;
using TweetLib.Api.Data;
using TweetLib.Api.Data.Notification;
using TweetLib.Utils.Serialization.Converters;
using TweetLib.Utils.Static;
namespace TweetDuck.Browser.Notification {
abstract class NotificationScreen {
public static List<NotificationScreen> All {
get {
var list = new List<NotificationScreen> {
SameAsTweetDuck.Instance
};
for (int index = 1; index <= Screen.AllScreens.Length; index++) {
list.Add(new Static(index));
}
foreach (var provider in ApiServices.Notifications.GetDesktopNotificationScreenProviders()) {
list.Add(new Provided(provider.NamespacedId));
}
return list;
}
}
public abstract string DisplayName { get; }
private NotificationScreen() {}
public abstract Screen PickScreen(FormBrowser mainWindow);
protected abstract string Serialize();
public sealed class SameAsTweetDuck : NotificationScreen {
public static SameAsTweetDuck Instance { get; } = new SameAsTweetDuck();
public override string DisplayName => "(Same as TweetDuck)";
private SameAsTweetDuck() {}
public override Screen PickScreen(FormBrowser mainWindow) {
return Screen.FromControl(mainWindow);
}
protected override string Serialize() {
return "0";
}
public override bool Equals(object? obj) {
return obj is SameAsTweetDuck;
}
public override int GetHashCode() {
return 1828695039;
}
}
private sealed class Static : NotificationScreen {
public override string DisplayName {
get {
Screen? screen = Screen;
if (screen == null) {
return $"Unknown ({screenIndex})";
}
return screen.DeviceName.TrimStart('\\', '.') + $" ({screen.Bounds.Width}x{screen.Bounds.Height})";
}
}
private Screen? Screen => screenIndex >= 1 && screenIndex <= Screen.AllScreens.Length ? Screen.AllScreens[screenIndex - 1] : null;
private readonly int screenIndex;
public Static(int screenIndex) {
this.screenIndex = screenIndex;
}
public override Screen PickScreen(FormBrowser mainWindow) {
return Screen ?? SameAsTweetDuck.Instance.PickScreen(mainWindow);
}
protected override string Serialize() {
return screenIndex.ToString();
}
public override bool Equals(object? obj) {
return obj is Static other && screenIndex == other.screenIndex;
}
public override int GetHashCode() {
return 31 * screenIndex;
}
}
private sealed class Provided : NotificationScreen {
public override string DisplayName => Provider?.DisplayName ?? $"Unknown ({resource})";
private readonly NamespacedResource resource;
private NotificationService.NamespacedProvider? provider;
private NotificationService.NamespacedProvider? Provider => provider ??= ApiServices.Notifications.GetDesktopNotificationScreenProviders().Find(p => p.NamespacedId == resource);
public Provided(NamespacedResource resource) {
this.resource = resource;
}
public override Screen PickScreen(FormBrowser mainWindow) {
IScreen? pick = Provider?.PickScreen(new WindowsFormsScreenLayout(mainWindow));
return pick is WindowsFormsScreen screen ? screen.Screen : SameAsTweetDuck.Instance.PickScreen(mainWindow); // TODO
}
protected override string Serialize() {
return resource.Namespace + ":" + resource.Path;
}
public override bool Equals(object? obj) {
return obj is Provided other && resource == other.resource;
}
public override int GetHashCode() {
return resource.GetHashCode();
}
private sealed class WindowsFormsScreenLayout : IScreenLayout {
public IScreen PrimaryScreen => new WindowsFormsScreen(Screen.PrimaryScreen);
public IScreen TweetDuckScreen => new WindowsFormsScreen(Screen.FromControl(mainWindow));
public List<IScreen> AllScreens => Screen.AllScreens.Select(static screen => new WindowsFormsScreen(screen)).ToList<IScreen>();
private readonly FormBrowser mainWindow;
public WindowsFormsScreenLayout(FormBrowser mainWindow) {
this.mainWindow = mainWindow;
}
}
private sealed class WindowsFormsScreen : IScreen {
public Screen Screen { get; }
public ScreenBounds Bounds { get; }
public string Name => Screen.DeviceName;
public bool IsPrimary => Screen.Primary;
public WindowsFormsScreen(Screen screen) {
this.Screen = screen;
this.Bounds = new ScreenBounds(screen.Bounds.X, screen.Bounds.Y, screen.Bounds.Width, screen.Bounds.Height);
}
}
}
public static readonly BasicTypeConverter<NotificationScreen> Converter = new() {
ConvertToString = static value => value.Serialize(),
ConvertToObject = static value => {
if (value == "0") {
return SameAsTweetDuck.Instance;
}
else if (int.TryParse(value, out int index)) {
return new Static(index);
}
var resource = StringUtils.SplitInTwo(value, ':');
if (resource != null) {
return new Provided(new NamespacedResource(new Resource(resource.Value.before), new Resource(resource.Value.after)));
}
return SameAsTweetDuck.Instance;
}
};
}
}

View File

@ -2,6 +2,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using TweetDuck.Browser;
using TweetDuck.Browser.Notification;
using TweetDuck.Controls;
using TweetLib.Core;
using TweetLib.Core.Application;
@ -66,7 +67,7 @@ namespace TweetDuck.Configuration {
public DesktopNotification.Position NotificationPosition { get; set; } = DesktopNotification.Position.TopRight;
public Point CustomNotificationPosition { get; set; } = ControlExtensions.InvisibleLocation;
public int NotificationDisplay { get; set; } = 0;
public NotificationScreen NotificationDisplay { get; set; } = NotificationScreen.SameAsTweetDuck.Instance;
public int NotificationEdgeDistance { get; set; } = 8;
public int NotificationWindowOpacity { get; set; } = 100;

View File

@ -86,13 +86,21 @@ namespace TweetDuck.Dialogs.Settings {
}
comboBoxDisplay.Enabled = trackBarEdgeDistance.Enabled = !radioLocCustom.Checked;
comboBoxDisplay.Items.Add("(Same as TweetDuck)");
foreach (Screen screen in Screen.AllScreens) {
comboBoxDisplay.Items.Add($"{screen.DeviceName.TrimStart('\\', '.')} ({screen.Bounds.Width}x{screen.Bounds.Height})");
bool foundScreen = false;
foreach (var screen in NotificationScreen.All) {
comboBoxDisplay.Items.Add(new NotificationScreenItem(screen));
if (screen.Equals(Config.NotificationDisplay)) {
comboBoxDisplay.SelectedIndex = comboBoxDisplay.Items.Count - 1;
foundScreen = true;
}
}
comboBoxDisplay.SelectedIndex = Math.Min(comboBoxDisplay.Items.Count - 1, Config.NotificationDisplay);
if (!foundScreen) {
comboBoxDisplay.Items.Add(new NotificationScreenItem(Config.NotificationDisplay));
comboBoxDisplay.SelectedIndex = comboBoxDisplay.Items.Count - 1;
}
trackBarEdgeDistance.SetValueSafe(Config.NotificationEdgeDistance);
labelEdgeDistanceValue.Text = trackBarEdgeDistance.Value + " px";
@ -279,7 +287,7 @@ namespace TweetDuck.Dialogs.Settings {
}
private void comboBoxDisplay_SelectedValueChanged(object? sender, EventArgs e) {
Config.NotificationDisplay = comboBoxDisplay.SelectedIndex;
Config.NotificationDisplay = ((NotificationScreenItem) comboBoxDisplay.SelectedItem).Screen;
notification.ShowExampleNotification(false);
}
@ -289,6 +297,21 @@ namespace TweetDuck.Dialogs.Settings {
notification.ShowExampleNotification(false);
}
private class NotificationScreenItem {
public NotificationScreen Screen { get; }
private readonly string displayName;
public NotificationScreenItem(NotificationScreen screen) {
this.Screen = screen;
this.displayName = screen.DisplayName;
}
public override string ToString() {
return displayName;
}
}
#endregion
#region Size

View File

@ -6,6 +6,7 @@ using CefSharp.WinForms;
using TweetDuck.Application;
using TweetDuck.Browser;
using TweetDuck.Browser.Base;
using TweetDuck.Browser.Notification;
using TweetDuck.Configuration;
using TweetDuck.Dialogs;
using TweetDuck.Management;
@ -16,6 +17,7 @@ using TweetLib.Browser.CEF.Utils;
using TweetLib.Browser.Request;
using TweetLib.Core;
using TweetLib.Core.Application;
using TweetLib.Core.Features.Extensions;
using TweetLib.Core.Features.Plugins;
using TweetLib.Core.Features.Plugins.Config;
using TweetLib.Core.Features.TweetDeck;
@ -90,6 +92,7 @@ namespace TweetDuck {
public string? ResourceRewriteRules => Arguments.GetValue(Arguments.ArgFreeze);
public ConfigManager CreateConfigManager(string storagePath) {
ConfigManager.ConverterRegistry.Register(typeof(NotificationScreen), NotificationScreen.Converter);
return new ConfigManager<UserConfig, SystemConfig>(storagePath, Config);
}
@ -137,6 +140,9 @@ namespace TweetDuck {
Cef.Initialize(settings, false, new BrowserProcessHandler());
Win.Application.ApplicationExit += static (_, _) => ExitCleanup();
ApiServices.Register();
ExtensionLoader.LoadAllInFolder(App.ExtensionPath);
var updateCheckClient = new UpdateCheckClient(Path.Combine(storagePath, InstallerFolder));
var mainForm = new FormBrowser(resourceCache, pluginManager, updateCheckClient, lockManager.WindowRestoreMessage);
Win.Application.Run(mainForm);

View File

@ -40,6 +40,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\lib\TweetLib.Api\TweetLib.Api.csproj" />
<ProjectReference Include="..\..\lib\TweetLib.Browser.CEF\TweetLib.Browser.CEF.csproj" />
<ProjectReference Include="..\..\lib\TweetLib.Browser\TweetLib.Browser.csproj" />
<ProjectReference Include="..\..\lib\TweetLib.Communication\TweetLib.Communication.csproj" />

View File

@ -23,6 +23,7 @@ namespace TweetDuck.Updates {
FileName = Path,
Arguments = arguments,
Verb = runElevated ? "runas" : string.Empty,
UseShellExecute = true,
ErrorDialog = true
})) {
return true;

View File

@ -59,11 +59,15 @@ namespace TweetImpl.CefSharp.Component {
}
private void OnFrameLoadStart(object? sender, FrameLoadStartEventArgs e) {
base.OnFrameLoadStart(e.Url, e.Frame);
if (!string.IsNullOrEmpty(e.Url)) {
base.OnFrameLoadStart(e.Url, e.Frame);
}
}
private void OnFrameLoadEnd(object? sender, FrameLoadEndEventArgs e) {
base.OnFrameLoadEnd(e.Url, e.Frame);
if (!string.IsNullOrEmpty(e.Url)) {
base.OnFrameLoadEnd(e.Url, e.Frame);
}
}
}
}