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

Compare commits

...

13 Commits

65 changed files with 673 additions and 95 deletions

1
.gitignore vendored
View File

@@ -12,6 +12,7 @@ bld/*
# Rider
**/.idea/dictionaries
**/.idea/misc.xml
**/.idea/riderMarkupCache.xml
# User-specific files
*.suo

View File

@@ -12,7 +12,7 @@
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value="net6.0-windows7.0" />
<option name="PROJECT_TFM" value="net7.0-windows" />
<method v="2">
<option name="Build" />
</method>

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.24";
public const string Tag = "1.25.1";
}
}

BIN
bld/Redist/concrt140.dll Normal file

Binary file not shown.

BIN
bld/Redist/msvcp140.dll Normal file

Binary file not shown.

BIN
bld/Redist/msvcp140_1.dll Normal file

Binary file not shown.

BIN
bld/Redist/msvcp140_2.dll Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
bld/Redist/vccorlib140.dll Normal file

Binary file not shown.

BIN
bld/Redist/vcruntime140.dll Normal file

Binary file not shown.

View File

@@ -1,18 +1,21 @@
; Script generated by the Inno Script Studio Wizard.
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
#define MyAppID "8C25A716-7E11-4AAD-9992-8B5D0C78AE06"
#define MyAppName "TweetDuck"
#define MyAppPublisher "chylex"
#define MyAppURL "https://tweetduck.chylex.com"
#define MyAppShortURL "https://td.chylex.com"
#define MyAppExeName "TweetDuck.exe"
#define MyAppVersion GetFileVersion("..\windows\TweetDuck\bin\x86\Release\TweetDuck.exe")
#define MyAppArchitecture "x86"
#define MyAppVersion GetFileVersion("..\windows\TweetDuck\bin\" + MyAppArchitecture + "\Release\TweetDuck.exe")
#include ReadReg(HKLM, "Software\Mitrich Software\Inno Download Plugin", "InstallDir") + "\idp.iss"
[Setup]
AppId={{8C25A716-7E11-4AAD-9992-8B5D0C78AE06}
AppId={{{#MyAppID}}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
AppVerName={#MyAppName} {#MyAppVersion}
@@ -43,8 +46,8 @@ Name: "english"; MessagesFile: "compiler:Default.isl"
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalTasks}"; Flags: unchecked
[Files]
Source: "..\windows\TweetDuck\bin\x86\Release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\windows\TweetDuck\bin\x86\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
Source: "..\windows\TweetDuck\bin\{#MyAppArchitecture}\Release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\windows\TweetDuck\bin\{#MyAppArchitecture}\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
[Icons]
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Check: TDIsUninstallable

View File

@@ -1,18 +1,21 @@
; Script generated by the Inno Script Studio Wizard.
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
#define MyAppID "8C25A716-7E11-4AAD-9992-8B5D0C78AE06"
#define MyAppName "TweetDuck"
#define MyAppPublisher "chylex"
#define MyAppURL "https://tweetduck.chylex.com"
#define MyAppShortURL "https://td.chylex.com"
#define MyAppExeName "TweetDuck.exe"
#define MyAppVersion GetFileVersion("..\windows\TweetDuck\bin\x86\Release\TweetDuck.exe")
#define MyAppArchitecture "x86"
#define MyAppVersion GetFileVersion("..\windows\TweetDuck\bin\" + MyAppArchitecture + "\Release\TweetDuck.exe")
#include ReadReg(HKLM, "Software\Mitrich Software\Inno Download Plugin", "InstallDir") + "\idp.iss"
[Setup]
AppId={{8C25A716-7E11-4AAD-9992-8B5D0C78AE06}
AppId={{{#MyAppID}}
AppName={#MyAppName} Portable
AppVersion={#MyAppVersion}
AppVerName={#MyAppName} {#MyAppVersion}
@@ -40,8 +43,8 @@ MinVersion=0,6.1
Name: "english"; MessagesFile: "compiler:Default.isl"
[Files]
Source: "..\windows\TweetDuck\bin\x86\Release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\windows\TweetDuck\bin\x86\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
Source: "..\windows\TweetDuck\bin\{#MyAppArchitecture}\Release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\windows\TweetDuck\bin\{#MyAppArchitecture}\Release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
[Run]
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall shellexec skipifsilent

View File

@@ -1,15 +1,17 @@
; Script generated by the Inno Script Studio Wizard.
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
#define MyAppID "8C25A716-7E11-4AAD-9992-8B5D0C78AE06"
#define MyAppName "TweetDuck"
#define MyAppPublisher "chylex"
#define MyAppURL "https://tweetduck.chylex.com"
#define MyAppShortURL "https://td.chylex.com"
#define MyAppExeName "TweetDuck.exe"
#define MyAppID "8C25A716-7E11-4AAD-9992-8B5D0C78AE06"
#define MyAppVersion GetFileVersion("..\windows\TweetDuck\bin\x86\Release\TweetDuck.exe")
#define CefVersion GetFileVersion("..\windows\TweetDuck\bin\x86\Release\libcef.dll")
#define MyAppArchitecture "x86"
#define MyAppVersion GetFileVersion("..\windows\TweetDuck\bin\" + MyAppArchitecture + "\Release\TweetDuck.exe")
#define CefVersion GetFileVersion("..\windows\TweetDuck\bin\" + MyAppArchitecture + "\Release\libcef.dll")
#include ReadReg(HKLM, "Software\Mitrich Software\Inno Download Plugin", "InstallDir") + "\idp.iss"
@@ -43,13 +45,13 @@ MinVersion=0,6.1
Name: "english"; MessagesFile: "compiler:Default.isl"
[Files]
Source: "..\windows\TweetDuck\bin\x86\Release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\windows\TweetDuck\bin\x86\Release\TweetDuck.*"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\windows\TweetDuck\bin\x86\Release\TweetImpl.*"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\windows\TweetDuck\bin\x86\Release\TweetLib.*"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\windows\TweetDuck\bin\x86\Release\guide\*.*"; DestDir: "{app}\guide"; Flags: ignoreversion recursesubdirs createallsubdirs
Source: "..\windows\TweetDuck\bin\x86\Release\resources\*.*"; DestDir: "{app}\resources"; Flags: ignoreversion recursesubdirs createallsubdirs
Source: "..\windows\TweetDuck\bin\x86\Release\plugins\*.*"; DestDir: "{app}\plugins"; Flags: ignoreversion recursesubdirs createallsubdirs
Source: "..\windows\TweetDuck\bin\{#MyAppArchitecture}\Release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\windows\TweetDuck\bin\{#MyAppArchitecture}\Release\TweetDuck.*"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\windows\TweetDuck\bin\{#MyAppArchitecture}\Release\TweetImpl.*"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\windows\TweetDuck\bin\{#MyAppArchitecture}\Release\TweetLib.*"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\windows\TweetDuck\bin\{#MyAppArchitecture}\Release\guide\*.*"; DestDir: "{app}\guide"; Flags: ignoreversion recursesubdirs createallsubdirs
Source: "..\windows\TweetDuck\bin\{#MyAppArchitecture}\Release\resources\*.*"; DestDir: "{app}\resources"; Flags: ignoreversion recursesubdirs createallsubdirs
Source: "..\windows\TweetDuck\bin\{#MyAppArchitecture}\Release\plugins\*.*"; DestDir: "{app}\plugins"; Flags: ignoreversion recursesubdirs createallsubdirs
[Icons]
Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Check: TDIsUninstallable
@@ -69,16 +71,21 @@ Type: filesandordirs; Name: "{localappdata}\{#MyAppName}\GPUCache"
Type: files; Name: "{app}\CEFSHARP-LICENSE.txt"
Type: files; Name: "{app}\LICENSE.txt"
Type: files; Name: "{app}\README.txt"
Type: files; Name: "{app}\natives_blob.bin"
Type: files; Name: "{app}\cef.pak"
Type: files; Name: "{app}\cef_100_percent.pak"
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"
Type: filesandordirs; Name: "{app}\scripts"
Type: filesandordirs; Name: "{app}\swiftshader"
[Code]
function TDIsUninstallable: Boolean; forward;

View File

@@ -1,6 +1,6 @@
{
"sdk": {
"version": "6.0.0",
"version": "7.0.0",
"rollForward": "latestMinor",
"allowPrerelease": false
}

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

@@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<Configurations>Debug;Release</Configurations>
<Platforms>x86;x64</Platforms>
<LangVersion>10</LangVersion>
<LangVersion>11</LangVersion>
<Nullable>enable</Nullable>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>

View File

@@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<Configurations>Debug;Release</Configurations>
<Platforms>x86;x64</Platforms>
<LangVersion>10</LangVersion>
<LangVersion>11</LangVersion>
<Nullable>enable</Nullable>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>

View File

@@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<Platforms>x86</Platforms>
<LangVersion>10</LangVersion>
<LangVersion>11</LangVersion>
<Nullable>enable</Nullable>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>

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

@@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<Configurations>Debug;Release</Configurations>
<Platforms>x86;x64</Platforms>
<LangVersion>10</LangVersion>
<LangVersion>11</LangVersion>
<Nullable>enable</Nullable>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
@@ -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

@@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<Configurations>Debug;Release</Configurations>
<Platforms>x86;x64</Platforms>
<LangVersion>10</LangVersion>
<LangVersion>11</LangVersion>
<Nullable>enable</Nullable>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<Platforms>x86</Platforms>
<RuntimeIdentifiers>win7-x86;linux-x64</RuntimeIdentifiers>
</PropertyGroup>

View File

@@ -7,7 +7,7 @@ open Xunit
module RegexAccount =
module IsMatch =
let isMatch = TwitterUrls.RegexAccount.IsMatch
let isMatch: string -> bool = TwitterUrls.RegexAccount.IsMatch
[<Fact>]
let ``accepts HTTP protocol`` () =

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<Platforms>x86</Platforms>
<RuntimeIdentifiers>win7-x86;linux-x64</RuntimeIdentifiers>
</PropertyGroup>

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<Platforms>x86</Platforms>
<RuntimeIdentifiers>win7-x86;linux-x64</RuntimeIdentifiers>
</PropertyGroup>

View File

@@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<Configurations>Debug;Release</Configurations>
<Platforms>x64</Platforms>
<LangVersion>10</LangVersion>
<LangVersion>11</LangVersion>
<OutputType>WinExe</OutputType>
<Nullable>enable</Nullable>
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>

View File

@@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<Configurations>Debug;Release</Configurations>
<Platforms>x64</Platforms>
<LangVersion>10</LangVersion>
<LangVersion>11</LangVersion>
<Nullable>enable</Nullable>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>

View File

@@ -1,6 +1,6 @@
{
"sdk": {
"version": "6.0.0",
"version": "7.0.0",
"rollForward": "latestMinor",
"allowPrerelease": false
}

View File

@@ -1,17 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0-windows</TargetFramework>
<TargetFramework>net7.0-windows</TargetFramework>
<Configurations>Debug;Release</Configurations>
<Platforms>x86</Platforms>
<RuntimeIdentifier>win7-x86</RuntimeIdentifier>
<LangVersion>10</LangVersion>
<LangVersion>11</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup>
<OutputType>WinExe</OutputType>
<UseWindowsForms>true</UseWindowsForms>
<RootNamespace>TweetDuck.Browser</RootNamespace>
<AssemblyName>TweetDuck.Browser</AssemblyName>
<ApplicationIcon>..\TweetDuck\Resources\Images\icon.ico</ApplicationIcon>
@@ -33,14 +32,14 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CefSharp.WinForms.NETCore" Version="105.3.330" />
<PackageReference Include="CefSharp.Common.NETCore" Version="107.1.90" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\Version.cs" Link="Version.cs" />
</ItemGroup>
<Target Name="CopyResources" AfterTargets="Build">
<Target Name="MakeBrowserProcessLargeAddressAware" AfterTargets="Build">
<Exec Command="call &quot;$(DevEnvDir)\..\..\VC\Auxiliary\Build\vcvars32.bat&quot; &amp; editbin /largeaddressaware /TSAWARE &quot;$(TargetDir)TweetDuck.Browser.exe&quot;" ContinueOnError="false" />
</Target>

View File

@@ -23,7 +23,7 @@ namespace TweetDuck.Video.Controls {
string? text = tooltipFunc(args);
if (text == null) {
if (text == null || control.Parent == null) {
Visible = false;
return;
}

View File

@@ -28,7 +28,7 @@ namespace TweetDuck.Video.Controls {
brushFore.Color = ForeColor;
brushHover.Color = Color.FromArgb(128, ForeColor);
brushOverlap.Color = Color.FromArgb(80 + ForeColor.R * 11 / 16, 80 + ForeColor.G * 11 / 16, 80 + ForeColor.B * 11 / 16);
brushBack.Color = Parent.BackColor;
brushBack.Color = Parent?.BackColor ?? Color.Black;
}
Rectangle rect = new Rectangle(0, 0, Width, Height);

View File

@@ -1,11 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0-windows</TargetFramework>
<TargetFramework>net7.0-windows</TargetFramework>
<Configurations>Debug;Release</Configurations>
<Platforms>x86</Platforms>
<RuntimeIdentifier>win7-x86</RuntimeIdentifier>
<LangVersion>10</LangVersion>
<LangVersion>11</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>

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

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using TweetDuck.Controls;
using TweetDuck.Management;
using TweetDuck.Utils;
using TweetLib.Browser.Interfaces;
@@ -53,12 +54,12 @@ namespace TweetDuck.Browser.Notification {
}
}
private void WindowsSessionManager_LockStateChanged(object? sender, EventArgs e) {
if (WindowsSessionManager.IsLocked) {
PauseNotification(NotificationPauseReason.WindowsSessionLocked);
private void WindowsSessionManager_LockStateChanged(object? sender, bool isLocked) {
if (isLocked) {
this.InvokeAsyncSafe(() => PauseNotification(NotificationPauseReason.WindowsSessionLocked));
}
else {
ResumeNotification(NotificationPauseReason.WindowsSessionLocked);
this.InvokeAsyncSafe(() => ResumeNotification(NotificationPauseReason.WindowsSessionLocked));
}
}

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

@@ -95,8 +95,8 @@ namespace TweetDuck.Controls {
form.WindowState = state.IsMaximized ? FormWindowState.Maximized : FormWindowState.Normal;
}
if ((state.Bounds == Rectangle.Empty && firstTimeFullscreen) || form.IsFullyOutsideView()) {
form.DesktopBounds = Screen.PrimaryScreen.WorkingArea;
if (((state.Bounds == Rectangle.Empty && firstTimeFullscreen) || form.IsFullyOutsideView()) && Screen.PrimaryScreen is {} primaryScreen) {
form.DesktopBounds = primaryScreen.WorkingArea;
form.WindowState = FormWindowState.Maximized;
state.Save(form);
}

View File

@@ -28,7 +28,7 @@ namespace TweetDuck.Dialogs {
}
private void OnLinkClicked(object? sender, LinkLabelLinkClickedEventArgs e) {
App.SystemHandler.OpenBrowser(e.Link.LinkData as string);
App.SystemHandler.OpenBrowser(e.Link?.LinkData as string);
}
private void FormAbout_HelpRequested(object? sender, HelpEventArgs hlpevent) {

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

@@ -4,8 +4,7 @@ using Win = System.Windows.Forms;
namespace TweetDuck.Management {
static class WindowsSessionManager {
public static bool IsLocked { get; private set; } = false;
public static event EventHandler? LockStateChanged;
public static event EventHandler<bool>? LockStateChanged;
public static void Register() {
Win.Application.ApplicationExit += OnApplicationExit;
@@ -27,8 +26,7 @@ namespace TweetDuck.Management {
}
private static void SetLocked(bool newState) {
IsLocked = newState;
LockStateChanged?.Invoke(null, EventArgs.Empty);
LockStateChanged?.Invoke(null, newState);
}
}
}

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;
@@ -45,6 +47,7 @@ namespace TweetDuck {
internal static void SetupWinForms() {
Win.Application.EnableVisualStyles();
Win.Application.SetCompatibleTextRenderingDefault(false);
Win.LegacyWinForms.EnsureValid();
}
[STAThread]
@@ -89,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);
}
@@ -136,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

@@ -1,11 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0-windows</TargetFramework>
<TargetFramework>net7.0-windows</TargetFramework>
<Configurations>Debug;Release</Configurations>
<Platforms>x86</Platforms>
<RuntimeIdentifier>win7-x86</RuntimeIdentifier>
<LangVersion>10</LangVersion>
<LangVersion>11</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
@@ -36,10 +36,11 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CefSharp.WinForms.NETCore" Version="105.3.330" />
<PackageReference Include="CefSharp.WinForms.NETCore" Version="107.1.90" />
</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" />
@@ -78,12 +79,17 @@
<ResourcesPlugins Remove="$(SolutionDir)resources\Plugins\.debug\**\*.*" />
<ResourcesPlugins Remove="$(SolutionDir)resources\Plugins\emoji-keyboard\emoji-instructions.txt" />
<ResourcesPluginsDebug Include="$(SolutionDir)resources\Plugins\.debug\**\*.*" Visible="false" />
<Redist Include="$(SolutionDir)bld\Redist\*.*" Visible="false" />
</ItemGroup>
<PropertyGroup>
<PreBuildEvent>powershell -NoProfile -Command "$ErrorActionPreference = 'SilentlyContinue'; (Get-Process TweetDuck.Browser | Where-Object {$_.Path -eq '$(TargetDir)TweetDuck.Browser.exe'}).Kill(); Exit 0"</PreBuildEvent>
</PropertyGroup>
<Target Name="MakeBrowserProcessLargeAddressAware" AfterTargets="Build">
<Exec Command="call &quot;$(DevEnvDir)\..\..\VC\Auxiliary\Build\vcvars32.bat&quot; &amp; editbin /largeaddressaware /TSAWARE &quot;$(TargetDir)TweetDuck.Browser.exe&quot;" ContinueOnError="false" />
</Target>
<Target Name="CopyResources" AfterTargets="Build">
<ItemGroup>
<LocalesToDelete Include="$(TargetDir)locales\*.pak" Exclude="$(TargetDir)locales\en-US.pak" Visible="false" />
@@ -120,6 +126,7 @@
<Copy SourceFiles="@(ResourcesContent)" DestinationFiles="@(ResourcesContent->'$(TargetDir)\resources\%(RecursiveDir)%(Filename)%(Extension)')" />
<Copy SourceFiles="@(ResourcesGuide)" DestinationFiles="@(ResourcesGuide->'$(TargetDir)\guide\%(RecursiveDir)%(Filename)%(Extension)')" />
<Copy SourceFiles="@(ResourcesPlugins)" DestinationFiles="@(ResourcesPlugins->'$(TargetDir)\plugins\official\%(RecursiveDir)%(Filename)%(Extension)')" />
<Copy SourceFiles="@(Redist)" DestinationFolder="$(TargetDir)" />
<Exec Command="powershell -NoProfile -ExecutionPolicy Unrestricted -File &quot;$(ProjectDir)Resources\PostBuild.ps1&quot; &quot;$(TargetDir)\&quot;" IgnoreExitCode="false" />
</Target>

View File

@@ -1,3 +1,4 @@
using System.Diagnostics;
using CefSharp;
using CefSharp.Handler;
using TweetImpl.CefSharp.Adapters;
@@ -7,11 +8,11 @@ namespace TweetImpl.CefSharp.Handlers {
sealed class CefRequestHandler : RequestHandler {
public RequestHandlerLogic<IRequest> Logic { get; }
private readonly bool autoReload;
private readonly AutoReloader? autoReloader;
public CefRequestHandler(CefLifeSpanHandler lifeSpanHandler, bool autoReload) {
this.Logic = new RequestHandlerLogic<IRequest>(CefRequestAdapter.Instance, lifeSpanHandler.Logic);
this.autoReload = autoReload;
this.autoReloader = autoReload ? new AutoReloader() : null;
}
protected override bool OnBeforeBrowse(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, bool userGesture, bool isRedirect) {
@@ -23,9 +24,31 @@ namespace TweetImpl.CefSharp.Handlers {
}
protected override void OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser, CefTerminationStatus status) {
if (autoReload) {
if (autoReloader?.RequestReload() == true) {
browser.Reload();
}
}
private sealed class AutoReloader {
private readonly Stopwatch lastReload = Stopwatch.StartNew();
private int rapidReloadCount;
public bool RequestReload() {
if (rapidReloadCount >= 2) {
lastReload.Stop();
return false;
}
if (lastReload.ElapsedMilliseconds < 5000) {
++rapidReloadCount;
}
else {
rapidReloadCount = 0;
}
lastReload.Restart();
return true;
}
}
}
}

View File

@@ -1,11 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0-windows</TargetFramework>
<TargetFramework>net7.0-windows</TargetFramework>
<Configurations>Debug;Release</Configurations>
<Platforms>x86</Platforms>
<RuntimeIdentifier>win7-x86</RuntimeIdentifier>
<LangVersion>10</LangVersion>
<LangVersion>11</LangVersion>
<Nullable>enable</Nullable>
</PropertyGroup>
@@ -28,7 +28,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CefSharp.WinForms.NETCore" Version="105.3.330" />
<PackageReference Include="CefSharp.WinForms.NETCore" Version="107.1.90" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,11 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0-windows</TargetFramework>
<TargetFramework>net7.0-windows</TargetFramework>
<Configurations>Debug;Release</Configurations>
<Platforms>x86</Platforms>
<RuntimeIdentifier>win7-x86</RuntimeIdentifier>
<LangVersion>10</LangVersion>
<LangVersion>11</LangVersion>
<Nullable>disable</Nullable>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>

View File

@@ -4,10 +4,16 @@ using System.Reflection;
namespace System.Windows.Forms {
internal sealed class Command2 {
private static readonly Type Type = typeof(Form).Assembly.GetType("System.Windows.Forms.Command");
private static readonly ConstructorInfo Constructor = Type.GetConstructor(new Type[] { typeof(ICommandExecutor) }) ?? throw new NullReferenceException();
private static readonly MethodInfo DisposeMethod = Type.GetMethod("Dispose", BindingFlags.Instance | BindingFlags.Public) ?? throw new NullReferenceException();
private static readonly PropertyInfo IDProperty = Type.GetProperty("ID") ?? throw new NullReferenceException();
private static readonly ConstructorInfo Constructor = Type.GetConstructor(new Type[] { typeof(ICommandExecutor) });
private static readonly MethodInfo DisposeMethod = Type.GetMethod("Dispose", BindingFlags.Instance | BindingFlags.Public);
private static readonly PropertyInfo IDProperty = Type.GetProperty("ID");
internal static void EnsureValid() {
if (Constructor == null || DisposeMethod == null || IDProperty == null) {
throw new InvalidOperationException();
}
}
public int ID { get; }
private readonly object cmd;

View File

@@ -7,8 +7,14 @@ using System.Runtime.InteropServices;
namespace System.Windows.Forms {
public sealed class ContextMenu : Menu {
private static readonly FieldInfo NotifyIconWindowField = typeof(NotifyIcon).GetField("window", BindingFlags.Instance | BindingFlags.NonPublic) ?? throw new InvalidOperationException();
private static readonly FieldInfo NotifyIconWindowField = typeof(NotifyIcon).GetField("_window", BindingFlags.Instance | BindingFlags.NonPublic);
internal static void EnsureValid() {
if (NotifyIconWindowField == null) {
throw new InvalidOperationException();
}
}
public event EventHandler Popup;
public void Show(Control control, Point pos) {

View File

@@ -0,0 +1,8 @@
namespace System.Windows.Forms;
public static class LegacyWinForms {
public static void EnsureValid() {
Command2.EnsureValid();
ContextMenu.EnsureValid();
}
}