Compare commits

..
13 Commits
65 changed files with 673 additions and 95 deletions
+1
View File
@@ -12,6 +12,7 @@ bld/*
# Rider
**/.idea/dictionaries
**/.idea/misc.xml
**/.idea/riderMarkupCache.xml
# User-specific files
*.suo
+1 -1
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>
+6
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
+1 -1
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";
}
}
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.
+7 -4
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
+7 -4
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
+18 -11
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;
+1 -1
View File
@@ -1,6 +1,6 @@
{
"sdk": {
"version": "6.0.0",
"version": "7.0.0",
"rollForward": "latestMinor",
"allowPrerelease": false
}
@@ -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}";
}
}
}
@@ -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);
}
}
@@ -0,0 +1,7 @@
namespace TweetLib.Api.Data.Notification {
public interface IScreen {
ScreenBounds Bounds { get; }
string Name { get; }
bool IsPrimary { get; }
}
}
@@ -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; }
}
}
@@ -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;
}
}
}
+43
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;
}
}
}
+5
View File
@@ -0,0 +1,5 @@
namespace TweetLib.Api {
public interface ITweetDuckApi {
T? FindService<T>() where T : class, ITweetDuckService;
}
}
+3
View File
@@ -0,0 +1,3 @@
namespace TweetLib.Api {
public interface ITweetDuckService {}
}
@@ -0,0 +1,7 @@
using TweetLib.Api.Data.Notification;
namespace TweetLib.Api.Service {
public interface INotificationService : ITweetDuckService {
void RegisterDesktopNotificationScreenProvider(IDesktopNotificationScreenProvider provider);
}
}
+15
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);
}
}
+16
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>
@@ -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>
+2 -2
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>
@@ -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>
+7 -2
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);
@@ -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;
}
}
}
@@ -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;
}
}
}
@@ -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> {
+3 -2
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>
+2 -2
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>
@@ -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>
@@ -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`` () =
+1 -1
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>
+1 -1
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>
+2 -2
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>
@@ -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>
+1 -1
View File
@@ -1,6 +1,6 @@
{
"sdk": {
"version": "6.0.0",
"version": "7.0.0",
"rollForward": "latestMinor",
"allowPrerelease": false
}
@@ -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>
@@ -23,7 +23,7 @@ namespace TweetDuck.Video.Controls {
string? text = tooltipFunc(args);
if (text == null) {
if (text == null || control.Parent == null) {
Visible = false;
return;
}
+1 -1
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);
@@ -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>
@@ -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);
}
}
}
@@ -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);
}
}
}
@@ -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) {
@@ -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));
}
}
@@ -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;
}
};
}
}
@@ -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;
@@ -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);
}
+1 -1
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) {
@@ -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
@@ -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);
}
}
}
+7
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);
+10 -3
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>
@@ -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;
}
}
}
}
@@ -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>
@@ -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>
@@ -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;
@@ -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) {
@@ -0,0 +1,8 @@
namespace System.Windows.Forms;
public static class LegacyWinForms {
public static void EnsureValid() {
Command2.EnsureValid();
ContextMenu.EnsureValid();
}
}