mirror of
https://github.com/MaaAssistantArknights/MaaAssistantArknights.git
synced 2026-07-01 01:10:34 +08:00
feat: Wine 支持 (#8960)
* feat: WineRuntimeInformation * fix: system theme crash under wine * refactor: toast notification * feat: add destory callback * feat: detect winegcc * feat: use native MaaCore under Wine * feat: libnotify integration * chore: disable hardware accelration under Wine * fix: distorted icon under Wine * chore: use Environment.ProcessPath instead of cursed alternatives * chore: don't filter *.exe when using native MaaCore * chore: force Aero2 theme * chore: allow build MaaWpfGui without Windows You need to extract native .NET SDK on top of Windows .NET SDK to get a working WPF SDK. * feat: fontconfig integration * docs: run MaaWpfGui under Wine
This commit is contained in:
@@ -13,6 +13,47 @@ icon: teenyicons:linux-alt-solid
|
||||
|
||||
[maa-cli](https://github.com/MaaAssistantArknights/maa-cli) 是一个使用 Rust 编写的简单 MAA 命令行工具。相关安装与使用教程请阅读[用户手册 - CLI使用指南](../CLI使用指南)。
|
||||
|
||||
### 使用 Wine
|
||||
|
||||
MAA WPF GUI 当前可以通过 Wine 运行。
|
||||
|
||||
#### 安装步骤
|
||||
|
||||
1. 前往 [.NET 发布页](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)下载并安装 Windows 版 .NET **桌面**运行时。
|
||||
|
||||
3. 下载 Windows 版 MAA,解压后运行 `wine MAA.exe`。
|
||||
|
||||
::: info 注意
|
||||
需要在连接设置中将 ADB 路径设置为 [Windows 版 `adb.exe`](https://dl.google.com/android/repository/platform-tools-latest-windows.zip)。
|
||||
|
||||
如果您需要通过 ADB 连接 USB 设备,请先在 Wine 外运行 `adb start-server`,即通过 Wine 连接原生 ADB server。
|
||||
:::
|
||||
|
||||
#### 使用 Linux 原生 MaaCore(实验性功能)
|
||||
|
||||
下载 [MAA Wine Bridge](https://github.com/MaaAssistantArknights/MaaAssistantArknights/tree/dev/src/MaaWineBridge) 源码并构建,用生成的 `MaaCore.dll`(ELF 文件)替换 Windows 版本,并将 Linux 原生动态库(`libMaaCore.so` 以及依赖)放在同一目录下。
|
||||
|
||||
此时通过 Wine 运行 `MAA.exe`,将会加载 Linux 原生动态库。
|
||||
|
||||
::: info 注意
|
||||
使用 Linux 原生 MaaCore 时,需要在连接设置中将 ADB 路径设置为 Linux 原生 ADB。
|
||||
:::
|
||||
|
||||
#### Linux 桌面整合(实验性功能)
|
||||
|
||||
桌面整合提供原生桌面通知支持,以及将 fontconfig 字体配置映射到 WPF 的功能。
|
||||
|
||||
将 MAA Wine Bridge 生成的 `MaaDesktopIntegration.so` 放到 `MAA.exe` 同目录下即可启用。
|
||||
|
||||
#### 已知问题
|
||||
|
||||
* Wine DirectWrite 强制启用 hinting,并且不将 DPI 传递给 FreeType,导致字体显示效果不佳。
|
||||
* 不使用原生桌面通知时,弹出通知会抢占全系统鼠标焦点,导致无法操作其他窗口。可以通过 `winecfg` 启用虚拟桌面模式缓解,或禁用桌面通知。
|
||||
* Wine-staging 用户需要关闭 `winecfg` 中的 `隐藏 Wine 版本` 选项,以便 MAA 正确检测 Wine 环境。
|
||||
* Wine 的 Light 主题会导致 WPF 中部分文字颜色异常,建议在 `winecfg` 中切换到无主题(Windows 经典主题)。
|
||||
* Wine 使用旧式 XEmbed 托盘图标,在 GNOME 下可能无法正常工作。
|
||||
* 使用 Linux 原生 MaaCore 时暂不支持自动更新(~~更新程序:我寻思我应该下载个 Windows 版~~)
|
||||
|
||||
### 使用 Python
|
||||
|
||||
#### 1. 安装 MAA 动态库
|
||||
|
||||
@@ -2,15 +2,17 @@
|
||||
|
||||
// The way how the function is called
|
||||
#if !defined(ASST_CALL)
|
||||
#if defined(_WIN32)
|
||||
#if defined(__WINE__) && defined(__x86_64__)
|
||||
#define ASST_CALL __attribute__((sysv_abi))
|
||||
#elif defined(_WIN32)
|
||||
#define ASST_CALL __stdcall
|
||||
#else
|
||||
#define ASST_CALL
|
||||
#endif /* _WIN32 */
|
||||
#endif /* __WINE__ / _WIN32 */
|
||||
#endif /* ASST_CALL */
|
||||
|
||||
// The function exported symbols
|
||||
#if defined _WIN32 || defined __CYGWIN__
|
||||
#if (defined(_WIN32) || defined(__CYGWIN__)) && !defined(__WINE__)
|
||||
#define ASST_DLL_IMPORT __declspec(dllimport)
|
||||
#define ASST_DLL_EXPORT __declspec(dllexport)
|
||||
#define ASST_DLL_LOCAL
|
||||
|
||||
@@ -101,6 +101,7 @@ Assistant::~Assistant()
|
||||
if (m_msg_thread.joinable()) {
|
||||
m_msg_thread.join();
|
||||
}
|
||||
m_callback(static_cast<AsstMsgId>(AsstMsg::Destroyed), "{}", m_callback_arg);
|
||||
}
|
||||
|
||||
bool asst::Assistant::set_instance_option(InstanceOptionKey key, const std::string& value)
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace asst
|
||||
ConnectionInfo, // 连接相关错误
|
||||
AllTasksCompleted, // 全部任务完成
|
||||
AsyncCallInfo, // 外部异步调用信息
|
||||
Destroyed, // 实例已销毁
|
||||
/* TaskChain Info */
|
||||
TaskChainError = 10000, // 任务链执行/识别错误
|
||||
TaskChainStart, // 任务链开始
|
||||
@@ -39,6 +40,7 @@ namespace asst
|
||||
{ AsstMsg::ConnectionInfo, "ConnectionInfo" },
|
||||
{ AsstMsg::AllTasksCompleted, "AllTasksCompleted" },
|
||||
{ AsstMsg::AsyncCallInfo, "AsyncCallInfo" },
|
||||
{ AsstMsg::Destroyed, "Destroyed" },
|
||||
/* TaskChain Info */
|
||||
{ AsstMsg::TaskChainError, "TaskChainError" },
|
||||
{ AsstMsg::TaskChainStart, "TaskChainStart" },
|
||||
|
||||
5
src/MaaWineBridge/CMakeLists.txt
Normal file
5
src/MaaWineBridge/CMakeLists.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
project(MaaWineBridge)
|
||||
|
||||
add_subdirectory(MaaCoreForwarder)
|
||||
add_subdirectory(MaaDesktopIntegration)
|
||||
43
src/MaaWineBridge/MaaCoreForwarder/CMakeLists.txt
Normal file
43
src/MaaWineBridge/MaaCoreForwarder/CMakeLists.txt
Normal file
@@ -0,0 +1,43 @@
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
project(MaaCoreForwarder C)
|
||||
|
||||
add_library(MaaCore_INTERFACE INTERFACE)
|
||||
set_target_properties(MaaCore_INTERFACE PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_SOURCE_DIR}/../../../include")
|
||||
|
||||
# fool cmake to add MaaCore.spec to linker command line
|
||||
add_library(MaaCore_SPEC IMPORTED STATIC)
|
||||
set_target_properties(MaaCore_SPEC PROPERTIES IMPORTED_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}/MaaCore.spec")
|
||||
|
||||
add_library(MaaCoreForwarder SHARED
|
||||
maacore.c
|
||||
dl_maacore.c
|
||||
entry.c
|
||||
task_queue.c
|
||||
task_queue_wine.c
|
||||
MaaCore.spec
|
||||
)
|
||||
|
||||
set_target_properties(MaaCoreForwarder PROPERTIES
|
||||
NO_SONAME TRUE
|
||||
BUILD_WITH_INSTALL_RPATH TRUE
|
||||
BUILD_RPATH_USE_ORIGIN TRUE # FIXME: not working
|
||||
INSTALL_RPATH "$ORIGIN"
|
||||
PREFIX ""
|
||||
OUTPUT_NAME "MaaCore"
|
||||
SUFFIX ".dll"
|
||||
)
|
||||
target_link_options(MaaCoreForwarder PRIVATE -Wl,--wine-builtin)
|
||||
target_link_libraries(MaaCoreForwarder MaaCore_INTERFACE MaaCore_SPEC)
|
||||
|
||||
# winegcc will force add .so suffix
|
||||
add_custom_command(TARGET MaaCoreForwarder POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E rename "$<TARGET_FILE:MaaCoreForwarder>.so" "$<TARGET_FILE:MaaCoreForwarder>"
|
||||
COMMENT "[Winelib hack] Rename $<TARGET_FILE_NAME:MaaCoreForwarder>.so to $<TARGET_FILE_NAME:MaaCoreForwarder>"
|
||||
)
|
||||
|
||||
install(TARGETS MaaCoreForwarder LIBRARY DESTINATION ".")
|
||||
|
||||
# or a good old fake module? (FIXME: not working)
|
||||
# add_library(MaaCoreForwarder_FAKE SHARED dummy.c)
|
||||
# set_target_properties(MaaCoreForwarder_FAKE PROPERTIES PREFIX "" OUTPUT_NAME "MaaCore" SUFFIX ".dll" NO_SONAME TRUE)
|
||||
# target_link_options(MaaCoreForwarder_FAKE PRIVATE -Wb,--fake-module)
|
||||
33
src/MaaWineBridge/MaaCoreForwarder/MaaCore.spec
Normal file
33
src/MaaWineBridge/MaaCoreForwarder/MaaCore.spec
Normal file
@@ -0,0 +1,33 @@
|
||||
@ stdcall AsstSetUserDir(ptr) WineShimAsstSetUserDir
|
||||
@ stdcall AsstLoadResource(ptr) WineShimAsstLoadResource
|
||||
@ stdcall AsstSetStaticOption(long ptr) WineShimAsstSetStaticOption
|
||||
|
||||
@ stdcall AsstCreate() WineShimAsstCreate
|
||||
@ stdcall AsstCreateEx(ptr ptr) WineShimAsstCreateEx
|
||||
@ stdcall AsstDestroy(ptr) WineShimAsstDestroy
|
||||
|
||||
@ stdcall AsstSetInstanceOption(ptr long ptr) WineShimAsstSetInstanceOption
|
||||
@ stdcall AsstConnect(ptr ptr ptr ptr) WineShimAsstConnect
|
||||
|
||||
@ stdcall AsstAppendTask(ptr ptr ptr) WineShimAsstAppendTask
|
||||
@ stdcall AsstSetTaskParams(ptr long ptr) WineShimAsstSetTaskParams
|
||||
|
||||
@ stdcall AsstStart(ptr) WineShimAsstStart
|
||||
@ stdcall AsstStop(ptr) WineShimAsstStop
|
||||
@ stdcall AsstRunning(ptr) WineShimAsstRunning
|
||||
@ stdcall AsstConnected(ptr) WineShimAsstConnected
|
||||
@ stdcall AsstBackToHome(ptr) WineShimAsstBackToHome
|
||||
|
||||
@ stdcall AsstAsyncConnect(ptr ptr ptr ptr long) WineShimAsstAsyncConnect
|
||||
@ stdcall AsstAsyncClick(ptr long long long) WineShimAsstAsyncClick
|
||||
@ stdcall AsstAsyncScreencap(ptr long) WineShimAsstAsyncScreencap
|
||||
|
||||
@ stdcall AsstGetImage(ptr ptr long) WineShimAsstGetImage
|
||||
@ stdcall AsstGetUUID(ptr ptr long) WineShimAsstGetUUID
|
||||
@ stdcall AsstGetTasksList(ptr ptr long) WineShimAsstGetTasksList
|
||||
@ stdcall AsstGetNullSize() WineShimAsstGetNullSize
|
||||
|
||||
@ stdcall AsstGetVersion() WineShimAsstGetVersion
|
||||
@ stdcall AsstLog(ptr ptr) WineShimAsstLog
|
||||
|
||||
@ cdecl dl_has_maacore()
|
||||
51
src/MaaWineBridge/MaaCoreForwarder/dl_maacore.c
Normal file
51
src/MaaWineBridge/MaaCoreForwarder/dl_maacore.c
Normal file
@@ -0,0 +1,51 @@
|
||||
#include <dlfcn.h>
|
||||
#define DL_FUNC_IMPL
|
||||
#include "dl_maacore.h"
|
||||
|
||||
void dl_init_maacore()
|
||||
{
|
||||
void* maacore = dlopen("libMaaCore.so", RTLD_NOW);
|
||||
|
||||
if (!maacore) {
|
||||
return;
|
||||
}
|
||||
|
||||
FILL_DL_FUNC(maacore, AsstSetUserDir)
|
||||
FILL_DL_FUNC(maacore, AsstLoadResource)
|
||||
FILL_DL_FUNC(maacore, AsstSetStaticOption)
|
||||
|
||||
FILL_DL_FUNC(maacore, AsstCreate)
|
||||
FILL_DL_FUNC(maacore, AsstCreateEx)
|
||||
FILL_DL_FUNC(maacore, AsstDestroy)
|
||||
|
||||
FILL_DL_FUNC(maacore, AsstSetInstanceOption)
|
||||
FILL_DL_FUNC(maacore, AsstConnect)
|
||||
|
||||
FILL_DL_FUNC(maacore, AsstAppendTask)
|
||||
FILL_DL_FUNC(maacore, AsstSetTaskParams)
|
||||
|
||||
FILL_DL_FUNC(maacore, AsstStart)
|
||||
FILL_DL_FUNC(maacore, AsstStop)
|
||||
FILL_DL_FUNC(maacore, AsstRunning)
|
||||
FILL_DL_FUNC(maacore, AsstConnected)
|
||||
FILL_DL_FUNC(maacore, AsstBackToHome)
|
||||
|
||||
FILL_DL_FUNC(maacore, AsstAsyncConnect)
|
||||
FILL_DL_FUNC(maacore, AsstAsyncClick)
|
||||
FILL_DL_FUNC(maacore, AsstAsyncScreencap)
|
||||
|
||||
FILL_DL_FUNC(maacore, AsstGetImage)
|
||||
FILL_DL_FUNC(maacore, AsstGetUUID)
|
||||
FILL_DL_FUNC(maacore, AsstGetTasksList)
|
||||
FILL_DL_FUNC(maacore, AsstGetNullSize)
|
||||
|
||||
FILL_DL_FUNC(maacore, AsstGetVersion)
|
||||
FILL_DL_FUNC(maacore, AsstLog)
|
||||
}
|
||||
|
||||
#include <windef.h>
|
||||
|
||||
int __cdecl dl_has_maacore()
|
||||
{
|
||||
return dl_AsstCreateEx != NULL;
|
||||
}
|
||||
37
src/MaaWineBridge/MaaCoreForwarder/dl_maacore.h
Normal file
37
src/MaaWineBridge/MaaCoreForwarder/dl_maacore.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#include <AsstCaller.h>
|
||||
|
||||
#include "dlshim.h"
|
||||
|
||||
DEFINE_DL_FUNC(AsstSetUserDir)
|
||||
DEFINE_DL_FUNC(AsstLoadResource)
|
||||
DEFINE_DL_FUNC(AsstSetStaticOption)
|
||||
|
||||
DEFINE_DL_FUNC(AsstCreate)
|
||||
DEFINE_DL_FUNC(AsstCreateEx)
|
||||
DEFINE_DL_FUNC(AsstDestroy)
|
||||
|
||||
DEFINE_DL_FUNC(AsstSetInstanceOption)
|
||||
DEFINE_DL_FUNC(AsstConnect)
|
||||
|
||||
DEFINE_DL_FUNC(AsstAppendTask)
|
||||
DEFINE_DL_FUNC(AsstSetTaskParams)
|
||||
|
||||
DEFINE_DL_FUNC(AsstStart)
|
||||
DEFINE_DL_FUNC(AsstStop)
|
||||
DEFINE_DL_FUNC(AsstRunning)
|
||||
DEFINE_DL_FUNC(AsstConnected)
|
||||
DEFINE_DL_FUNC(AsstBackToHome)
|
||||
|
||||
DEFINE_DL_FUNC(AsstAsyncConnect)
|
||||
DEFINE_DL_FUNC(AsstAsyncClick)
|
||||
DEFINE_DL_FUNC(AsstAsyncScreencap)
|
||||
|
||||
DEFINE_DL_FUNC(AsstGetImage)
|
||||
DEFINE_DL_FUNC(AsstGetUUID)
|
||||
DEFINE_DL_FUNC(AsstGetTasksList)
|
||||
DEFINE_DL_FUNC(AsstGetNullSize)
|
||||
|
||||
DEFINE_DL_FUNC(AsstGetVersion)
|
||||
DEFINE_DL_FUNC(AsstLog)
|
||||
|
||||
void dl_init_maacore();
|
||||
14
src/MaaWineBridge/MaaCoreForwarder/dlshim.h
Normal file
14
src/MaaWineBridge/MaaCoreForwarder/dlshim.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef DL_FUNC_IMPL
|
||||
#define DL_FUNC_DEF
|
||||
#else
|
||||
#define DL_FUNC_DEF extern
|
||||
#endif
|
||||
|
||||
#define DEFINE_DL_FUNC(func) DL_FUNC_DEF typeof(func)* dl_##func;
|
||||
|
||||
#define FILL_DL_FUNC(lib, func) \
|
||||
do { \
|
||||
dl_##func = (typeof(func)*)dlsym(lib, #func); \
|
||||
} while (0);
|
||||
7
src/MaaWineBridge/MaaCoreForwarder/entry.c
Normal file
7
src/MaaWineBridge/MaaCoreForwarder/entry.c
Normal file
@@ -0,0 +1,7 @@
|
||||
#include "dl_maacore.h"
|
||||
|
||||
static void lib_init() __attribute__((constructor));
|
||||
|
||||
void lib_init() {
|
||||
dl_init_maacore();
|
||||
}
|
||||
257
src/MaaWineBridge/MaaCoreForwarder/maacore.c
Normal file
257
src/MaaWineBridge/MaaCoreForwarder/maacore.c
Normal file
@@ -0,0 +1,257 @@
|
||||
#include "dl_maacore.h"
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// clang-format off
|
||||
// don't sort includes
|
||||
#include <windef.h>
|
||||
#include <winbase.h>
|
||||
#include <winnls.h>
|
||||
// clang-format on
|
||||
|
||||
#include "task_queue.h"
|
||||
#include "task_queue_wine.h"
|
||||
|
||||
#define AsstMsg_Destroyed (5)
|
||||
|
||||
static char* translate_utf8_path(const char* utf8_win_path)
|
||||
{
|
||||
int len = MultiByteToWideChar(CP_UTF8, 0, utf8_win_path, -1, NULL, 0);
|
||||
wchar_t* wpath = malloc(len * sizeof(wchar_t));
|
||||
MultiByteToWideChar(CP_UTF8, 0, utf8_win_path, -1, wpath, len);
|
||||
char* unix_path = wine_get_unix_file_name(wpath);
|
||||
free(wpath);
|
||||
return unix_path;
|
||||
}
|
||||
|
||||
static int win32_free(void* ptr)
|
||||
{
|
||||
return HeapFree(GetProcessHeap(), 0, ptr);
|
||||
}
|
||||
|
||||
typedef void(
|
||||
__stdcall* AsstApiCallbackMsAbi)(AsstMsgId msg, const char* details_json, void* custom_arg);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
AsstApiCallbackMsAbi x64abi_callback;
|
||||
void* custom_arg;
|
||||
TaskQueue* queue;
|
||||
} CallbackState;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
AsstApiCallbackMsAbi x64abi_callback;
|
||||
AsstMsgId msg;
|
||||
const char* details_json;
|
||||
void* custom_arg;
|
||||
TaskQueue* queue;
|
||||
} CallbackInvocation;
|
||||
|
||||
static CallbackState* cb_state_new()
|
||||
{
|
||||
CallbackState* fwd_state = malloc(sizeof(CallbackState));
|
||||
memset(fwd_state, 0, sizeof(CallbackState));
|
||||
TaskQueue* queue = task_queue_new(task_queue_worker_wine_new);
|
||||
if (!queue) {
|
||||
free(fwd_state);
|
||||
return NULL;
|
||||
}
|
||||
fwd_state->queue = queue;
|
||||
return fwd_state;
|
||||
}
|
||||
|
||||
static void cb_state_free(CallbackState* fwd_state)
|
||||
{
|
||||
task_queue_free(fwd_state->queue);
|
||||
free(fwd_state);
|
||||
}
|
||||
|
||||
static void invoke_helper(void* arg)
|
||||
{
|
||||
// called in win32 thread
|
||||
CallbackInvocation* invocation = arg;
|
||||
invocation->x64abi_callback(invocation->msg, invocation->details_json, invocation->custom_arg);
|
||||
}
|
||||
|
||||
static void UnixCallbackShim(AsstMsgId msg, const char* details_json, void* custom_arg)
|
||||
{
|
||||
CallbackState* state = (CallbackState*)custom_arg;
|
||||
CallbackInvocation invocation = {
|
||||
.x64abi_callback = state->x64abi_callback,
|
||||
.msg = msg,
|
||||
.details_json = details_json,
|
||||
.custom_arg = state->custom_arg,
|
||||
};
|
||||
Task* task = task_queue_push(state->queue, invoke_helper, &invocation);
|
||||
task_wait(task);
|
||||
task_release(task);
|
||||
if (msg == AsstMsg_Destroyed) {
|
||||
cb_state_free(state);
|
||||
}
|
||||
}
|
||||
|
||||
AsstBool __stdcall WineShimAsstSetUserDir(const char* path)
|
||||
{
|
||||
char* unix_path = translate_utf8_path(path);
|
||||
AsstBool result = dl_AsstSetUserDir(unix_path);
|
||||
win32_free(unix_path);
|
||||
return result;
|
||||
}
|
||||
|
||||
AsstBool __stdcall WineShimAsstLoadResource(const char* path)
|
||||
{
|
||||
char* unix_path = translate_utf8_path(path);
|
||||
AsstBool result = dl_AsstLoadResource(unix_path);
|
||||
win32_free(unix_path);
|
||||
return result;
|
||||
}
|
||||
|
||||
AsstBool __stdcall WineShimAsstSetStaticOption(AsstStaticOptionKey key, const char* value)
|
||||
{
|
||||
return dl_AsstSetStaticOption(key, value);
|
||||
}
|
||||
|
||||
AsstHandle __stdcall WineShimAsstCreate()
|
||||
{
|
||||
return dl_AsstCreate();
|
||||
}
|
||||
|
||||
AsstHandle __stdcall WineShimAsstCreateEx(AsstApiCallbackMsAbi callback, void* custom_arg)
|
||||
{
|
||||
CallbackState* fwd_state = cb_state_new();
|
||||
fwd_state->x64abi_callback = callback;
|
||||
fwd_state->custom_arg = custom_arg;
|
||||
if (!fwd_state) {
|
||||
return NULL;
|
||||
}
|
||||
// callback needs to be called in a wine thread
|
||||
AsstHandle result = dl_AsstCreateEx(UnixCallbackShim, fwd_state);
|
||||
return result;
|
||||
}
|
||||
|
||||
void __stdcall WineShimAsstDestroy(AsstHandle handle)
|
||||
{
|
||||
dl_AsstDestroy(handle);
|
||||
}
|
||||
|
||||
AsstBool __stdcall WineShimAsstSetInstanceOption(
|
||||
AsstHandle handle,
|
||||
AsstInstanceOptionKey key,
|
||||
const char* value)
|
||||
{
|
||||
return dl_AsstSetInstanceOption(handle, key, value);
|
||||
}
|
||||
|
||||
AsstBool __stdcall WineShimAsstConnect(
|
||||
AsstHandle handle,
|
||||
const char* adb_path,
|
||||
const char* address,
|
||||
const char* config)
|
||||
{
|
||||
char* unix_adb_path = translate_utf8_path(adb_path);
|
||||
AsstBool result = dl_AsstConnect(handle, unix_adb_path, address, config);
|
||||
HeapFree(GetProcessHeap(), 0, unix_adb_path);
|
||||
return result;
|
||||
}
|
||||
|
||||
AsstTaskId __stdcall WineShimAsstAppendTask(AsstHandle handle, const char* type, const char* params)
|
||||
{
|
||||
return dl_AsstAppendTask(handle, type, params);
|
||||
}
|
||||
|
||||
AsstBool __stdcall WineShimAsstSetTaskParams(AsstHandle handle, AsstTaskId id, const char* params)
|
||||
{
|
||||
return dl_AsstSetTaskParams(handle, id, params);
|
||||
}
|
||||
|
||||
AsstBool __stdcall WineShimAsstStart(AsstHandle handle)
|
||||
{
|
||||
return dl_AsstStart(handle);
|
||||
}
|
||||
|
||||
AsstBool __stdcall WineShimAsstStop(AsstHandle handle)
|
||||
{
|
||||
return dl_AsstStop(handle);
|
||||
}
|
||||
|
||||
AsstBool __stdcall WineShimAsstRunning(AsstHandle handle)
|
||||
{
|
||||
return dl_AsstRunning(handle);
|
||||
}
|
||||
|
||||
AsstBool __stdcall WineShimAsstConnected(AsstHandle handle)
|
||||
{
|
||||
return dl_AsstConnected(handle);
|
||||
}
|
||||
|
||||
AsstBool __stdcall WineShimAsstBackToHome(AsstHandle handle)
|
||||
{
|
||||
return dl_AsstBackToHome(handle);
|
||||
}
|
||||
|
||||
AsstAsyncCallId __stdcall WineShimAsstAsyncConnect(
|
||||
AsstHandle handle,
|
||||
const char* adb_path,
|
||||
const char* address,
|
||||
const char* config,
|
||||
AsstBool block)
|
||||
{
|
||||
char* unix_adb_path;
|
||||
if (strchr(adb_path, '/') != NULL || strchr(adb_path, '\\') != NULL) {
|
||||
unix_adb_path = translate_utf8_path(adb_path);
|
||||
}
|
||||
else {
|
||||
unix_adb_path = (char*)adb_path;
|
||||
}
|
||||
AsstAsyncCallId result = dl_AsstAsyncConnect(handle, unix_adb_path, address, config, block);
|
||||
if (unix_adb_path != adb_path) {
|
||||
win32_free(unix_adb_path);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
AsstAsyncCallId __stdcall WineShimAsstAsyncClick(
|
||||
AsstHandle handle,
|
||||
int32_t x,
|
||||
int32_t y,
|
||||
AsstBool block)
|
||||
{
|
||||
return dl_AsstAsyncClick(handle, x, y, block);
|
||||
}
|
||||
|
||||
AsstAsyncCallId __stdcall WineShimAsstAsyncScreencap(AsstHandle handle, AsstBool block)
|
||||
{
|
||||
return dl_AsstAsyncScreencap(handle, block);
|
||||
}
|
||||
|
||||
AsstSize __stdcall WineShimAsstGetImage(AsstHandle handle, void* buff, AsstSize buff_size)
|
||||
{
|
||||
return dl_AsstGetImage(handle, buff, buff_size);
|
||||
}
|
||||
|
||||
AsstSize __stdcall WineShimAsstGetUUID(AsstHandle handle, char* buff, AsstSize buff_size)
|
||||
{
|
||||
return dl_AsstGetUUID(handle, buff, buff_size);
|
||||
}
|
||||
|
||||
AsstSize __stdcall WineShimAsstGetTasksList(AsstHandle handle, AsstTaskId* buff, AsstSize buff_size)
|
||||
{
|
||||
return dl_AsstGetTasksList(handle, buff, buff_size);
|
||||
}
|
||||
|
||||
AsstSize __stdcall WineShimAsstGetNullSize()
|
||||
{
|
||||
return dl_AsstGetNullSize();
|
||||
}
|
||||
|
||||
const char* __stdcall WineShimAsstGetVersion()
|
||||
{
|
||||
return dl_AsstGetVersion();
|
||||
}
|
||||
|
||||
void __stdcall WineShimAsstLog(const char* level, const char* message)
|
||||
{
|
||||
dl_AsstLog(level, message);
|
||||
}
|
||||
163
src/MaaWineBridge/MaaCoreForwarder/task_queue.c
Normal file
163
src/MaaWineBridge/MaaCoreForwarder/task_queue.c
Normal file
@@ -0,0 +1,163 @@
|
||||
#include "task_queue.h"
|
||||
#include <pthread.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define MAX_TASKS 16
|
||||
#define MAX_WORKERS 1
|
||||
|
||||
struct _Task
|
||||
{
|
||||
TaskFunc function;
|
||||
void* arg;
|
||||
pthread_mutex_t mutex;
|
||||
pthread_cond_t cond_done;
|
||||
int done;
|
||||
atomic_int ref_count;
|
||||
};
|
||||
|
||||
struct _TaskQueue
|
||||
{
|
||||
Task* tasks[MAX_TASKS];
|
||||
int head;
|
||||
int tail;
|
||||
int stop;
|
||||
pthread_mutex_t mutex;
|
||||
pthread_cond_t cond_not_empty;
|
||||
pthread_cond_t cond_not_full;
|
||||
TaskQueueWorkerFactory worker_factory;
|
||||
int worker_count;
|
||||
TaskQueueWorker* workers[MAX_WORKERS];
|
||||
};
|
||||
|
||||
static Task* task_new(TaskFunc function, void* arg)
|
||||
{
|
||||
Task* task = calloc(1, sizeof(Task));
|
||||
task->function = function;
|
||||
task->arg = arg;
|
||||
pthread_mutex_init(&task->mutex, NULL);
|
||||
pthread_cond_init(&task->cond_done, NULL);
|
||||
task->done = 0;
|
||||
atomic_init(&task->ref_count, 1);
|
||||
return task;
|
||||
}
|
||||
|
||||
void task_addref(Task* task)
|
||||
{
|
||||
atomic_fetch_add(&task->ref_count, 1);
|
||||
}
|
||||
|
||||
void task_release(Task* task)
|
||||
{
|
||||
if (atomic_fetch_sub(&task->ref_count, 1) == 1) {
|
||||
pthread_mutex_destroy(&task->mutex);
|
||||
pthread_cond_destroy(&task->cond_done);
|
||||
free(task);
|
||||
}
|
||||
}
|
||||
|
||||
void task_invoke(Task* task)
|
||||
{
|
||||
pthread_mutex_lock(&task->mutex);
|
||||
task->function(task->arg);
|
||||
task->done = 1;
|
||||
pthread_cond_signal(&task->cond_done);
|
||||
pthread_mutex_unlock(&task->mutex);
|
||||
}
|
||||
|
||||
void task_wait(Task* task)
|
||||
{
|
||||
pthread_mutex_lock(&task->mutex);
|
||||
while (!task->done) {
|
||||
pthread_cond_wait(&task->cond_done, &task->mutex);
|
||||
}
|
||||
pthread_mutex_unlock(&task->mutex);
|
||||
}
|
||||
|
||||
TaskQueue* task_queue_new(TaskQueueWorkerFactory worker_factory)
|
||||
{
|
||||
TaskQueue* queue = calloc(1, sizeof(TaskQueue));
|
||||
queue->head = 0;
|
||||
queue->tail = 0;
|
||||
queue->stop = 0;
|
||||
queue->worker_factory = worker_factory;
|
||||
TaskQueueWorker* worker = worker_factory(queue);
|
||||
if (!worker) {
|
||||
free(queue);
|
||||
return NULL;
|
||||
}
|
||||
queue->workers[queue->worker_count++] = worker;
|
||||
|
||||
pthread_mutex_init(&queue->mutex, NULL);
|
||||
pthread_cond_init(&queue->cond_not_empty, NULL);
|
||||
pthread_cond_init(&queue->cond_not_full, NULL);
|
||||
return queue;
|
||||
}
|
||||
|
||||
void task_queue_free(TaskQueue* queue)
|
||||
{
|
||||
pthread_mutex_lock(&queue->mutex);
|
||||
queue->stop = 1;
|
||||
pthread_cond_broadcast(&queue->cond_not_empty);
|
||||
pthread_mutex_unlock(&queue->mutex);
|
||||
|
||||
for (int i = 0; i < queue->worker_count; i++) {
|
||||
TaskQueueWorker* worker = queue->workers[i];
|
||||
worker->join(worker);
|
||||
worker->release(worker);
|
||||
queue->workers[i] = NULL;
|
||||
}
|
||||
|
||||
pthread_mutex_destroy(&queue->mutex);
|
||||
pthread_cond_destroy(&queue->cond_not_empty);
|
||||
pthread_cond_destroy(&queue->cond_not_full);
|
||||
free(queue);
|
||||
}
|
||||
|
||||
Task* task_queue_push(TaskQueue* queue, TaskFunc function, void* arg)
|
||||
{
|
||||
if (queue->stop) {
|
||||
return NULL;
|
||||
}
|
||||
pthread_mutex_lock(&queue->mutex);
|
||||
// TODO: add worker if queue is full
|
||||
while ((queue->tail + 1) % MAX_TASKS == queue->head) {
|
||||
pthread_cond_wait(&queue->cond_not_full, &queue->mutex);
|
||||
}
|
||||
Task* task = task_new(function, arg);
|
||||
|
||||
queue->tasks[queue->tail] = task;
|
||||
queue->tail = (queue->tail + 1) % MAX_TASKS;
|
||||
|
||||
pthread_cond_signal(&queue->cond_not_empty);
|
||||
pthread_mutex_unlock(&queue->mutex);
|
||||
task_addref(task);
|
||||
// 1 reference for the queue, 1 for the caller
|
||||
return task;
|
||||
}
|
||||
|
||||
Task* task_queue_pop(TaskQueue* queue)
|
||||
{
|
||||
if (queue->stop) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&queue->mutex);
|
||||
|
||||
while (queue->head == queue->tail && !queue->stop) {
|
||||
pthread_cond_wait(&queue->cond_not_empty, &queue->mutex);
|
||||
}
|
||||
|
||||
if (queue->stop) {
|
||||
pthread_mutex_unlock(&queue->mutex);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
Task* task = queue->tasks[queue->head];
|
||||
queue->head = (queue->head + 1) % MAX_TASKS;
|
||||
|
||||
pthread_cond_signal(&queue->cond_not_full);
|
||||
pthread_mutex_unlock(&queue->mutex);
|
||||
|
||||
return task;
|
||||
}
|
||||
28
src/MaaWineBridge/MaaCoreForwarder/task_queue.h
Normal file
28
src/MaaWineBridge/MaaCoreForwarder/task_queue.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
typedef struct _Task Task;
|
||||
typedef struct _TaskQueue TaskQueue;
|
||||
typedef struct _TaskQueueWorker TaskQueueWorker;
|
||||
|
||||
struct _TaskQueueWorker
|
||||
{
|
||||
TaskQueue* queue;
|
||||
void (*addref)(TaskQueueWorker*);
|
||||
void (*release)(TaskQueueWorker*);
|
||||
void (*join)(TaskQueueWorker*);
|
||||
};
|
||||
|
||||
typedef void (*TaskFunc)(void*);
|
||||
typedef TaskQueueWorker* (*TaskQueueWorkerFactory)(TaskQueue*);
|
||||
|
||||
TaskQueue* task_queue_new(TaskQueueWorkerFactory worker_factory);
|
||||
void task_queue_free(TaskQueue* queue);
|
||||
Task* task_queue_push(TaskQueue* queue, TaskFunc function, void* arg);
|
||||
Task* task_queue_pop(TaskQueue* queue);
|
||||
|
||||
void thread_pool_worker(TaskQueue* queue);
|
||||
|
||||
void task_wait(Task* task);
|
||||
void task_invoke(Task* task);
|
||||
void task_addref(Task* task);
|
||||
void task_release(Task* task);
|
||||
70
src/MaaWineBridge/MaaCoreForwarder/task_queue_wine.c
Normal file
70
src/MaaWineBridge/MaaCoreForwarder/task_queue_wine.c
Normal file
@@ -0,0 +1,70 @@
|
||||
#include "task_queue_wine.h"
|
||||
#include <pthread.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// clang-format off
|
||||
#include <windef.h>
|
||||
#include <winbase.h>
|
||||
|
||||
// clang-format on
|
||||
|
||||
typedef struct _TaskQueueWorkerWine
|
||||
{
|
||||
TaskQueueWorker base;
|
||||
atomic_int ref_count;
|
||||
pthread_t posix_thread;
|
||||
} TaskQueueWorkerWine;
|
||||
|
||||
static void task_queue_worker_wine_addref(TaskQueueWorker* worker)
|
||||
{
|
||||
TaskQueueWorkerWine* worker_wine = (TaskQueueWorkerWine*)worker;
|
||||
atomic_fetch_add(&worker_wine->ref_count, 1);
|
||||
}
|
||||
|
||||
static void task_queue_worker_wine_release(TaskQueueWorker* worker)
|
||||
{
|
||||
TaskQueueWorkerWine* worker_wine = (TaskQueueWorkerWine*)worker;
|
||||
if (atomic_fetch_sub(&worker_wine->ref_count, 1) == 1) {
|
||||
free(worker_wine);
|
||||
}
|
||||
}
|
||||
|
||||
static void task_queue_worker_wine_join(TaskQueueWorker* worker)
|
||||
{
|
||||
TaskQueueWorkerWine* worker_wine = (TaskQueueWorkerWine*)worker;
|
||||
pthread_join(worker_wine->posix_thread, NULL);
|
||||
}
|
||||
|
||||
static DWORD WINAPI worker(void* arg)
|
||||
{
|
||||
TaskQueueWorkerWine* worker = arg;
|
||||
worker->posix_thread = pthread_self();
|
||||
TaskQueue* queue = worker->base.queue;
|
||||
while (1) {
|
||||
Task* task = task_queue_pop(queue);
|
||||
if (task == NULL) {
|
||||
break;
|
||||
}
|
||||
task_invoke(task);
|
||||
task_release(task);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
TaskQueueWorker* task_queue_worker_wine_new(TaskQueue* queue)
|
||||
{
|
||||
TaskQueueWorkerWine* worker_wine = malloc(sizeof(TaskQueueWorkerWine));
|
||||
worker_wine->base.queue = queue;
|
||||
worker_wine->base.addref = task_queue_worker_wine_addref;
|
||||
worker_wine->base.release = task_queue_worker_wine_release;
|
||||
worker_wine->base.join = task_queue_worker_wine_join;
|
||||
atomic_init(&worker_wine->ref_count, 1);
|
||||
HANDLE thr = CreateThread(NULL, 0, worker, worker_wine, 0, NULL);
|
||||
if (thr == NULL) {
|
||||
free(worker_wine);
|
||||
return NULL;
|
||||
}
|
||||
CloseHandle(thr);
|
||||
return (TaskQueueWorker*)worker_wine;
|
||||
}
|
||||
3
src/MaaWineBridge/MaaCoreForwarder/task_queue_wine.h
Normal file
3
src/MaaWineBridge/MaaCoreForwarder/task_queue_wine.h
Normal file
@@ -0,0 +1,3 @@
|
||||
#include "task_queue.h"
|
||||
|
||||
TaskQueueWorker* task_queue_worker_wine_new(TaskQueue* queue);
|
||||
31
src/MaaWineBridge/MaaDesktopIntegration/CMakeLists.txt
Normal file
31
src/MaaWineBridge/MaaDesktopIntegration/CMakeLists.txt
Normal file
@@ -0,0 +1,31 @@
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
project(MaaDesktopIntegration C)
|
||||
|
||||
find_package(Fontconfig REQUIRED)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
|
||||
pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
|
||||
pkg_check_modules(GDK_PIXBUF REQUIRED IMPORTED_TARGET gdk-pixbuf-2.0)
|
||||
pkg_check_modules(LIBNOTIFY REQUIRED IMPORTED_TARGET libnotify)
|
||||
|
||||
add_library(MaaDesktopIntegration_SPEC IMPORTED STATIC)
|
||||
set_target_properties(MaaDesktopIntegration_SPEC PROPERTIES IMPORTED_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}/MaaDesktopIntegration.spec")
|
||||
|
||||
add_library(MaaDesktopIntegration SHARED
|
||||
notification.c
|
||||
main_loop.c
|
||||
gobject.c
|
||||
gdk-pixbuf.c
|
||||
fontconfig.c
|
||||
MaaDesktopIntegration.spec
|
||||
)
|
||||
|
||||
set_target_properties(MaaDesktopIntegration PROPERTIES
|
||||
NO_SONAME TRUE
|
||||
PREFIX ""
|
||||
OUTPUT_NAME "MaaDesktopIntegration"
|
||||
SUFFIX ".so"
|
||||
)
|
||||
target_link_libraries(MaaDesktopIntegration PkgConfig::GLIB PkgConfig::GDK_PIXBUF PkgConfig::LIBNOTIFY Fontconfig::Fontconfig MaaDesktopIntegration_SPEC)
|
||||
|
||||
install(TARGETS MaaDesktopIntegration LIBRARY DESTINATION ".")
|
||||
@@ -0,0 +1,42 @@
|
||||
@ cdecl g_object_unref(ptr) maa_wine_bridge_g_object_unref
|
||||
@ cdecl g_signal_connect_data(ptr str ptr ptr ptr long) maa_wine_bridge_g_signal_connect_data
|
||||
|
||||
@ cdecl notify_init(str) maa_wine_bridge_notify_init
|
||||
@ cdecl notify_notification_new(str str str) maa_wine_bridge_notify_notification_new
|
||||
@ cdecl notify_notification_add_action(ptr str str ptr ptr ptr) maa_wine_bridge_notify_notification_add_action
|
||||
@ cdecl notify_notification_clear_actions(ptr) maa_wine_bridge_notify_notification_clear_actions
|
||||
@ cdecl notify_notification_clear_hints(ptr) maa_wine_bridge_notify_notification_clear_hints
|
||||
@ cdecl notify_notification_close(ptr ptr) maa_wine_bridge_notify_notification_close
|
||||
@ cdecl notify_notification_get_activation_token(ptr) maa_wine_bridge_notify_notification_get_activation_token
|
||||
@ cdecl notify_notification_get_closed_reason(ptr) maa_wine_bridge_notify_notification_get_closed_reason
|
||||
@ cdecl notify_notification_set_app_name(ptr str) maa_wine_bridge_notify_notification_set_app_name
|
||||
@ cdecl notify_notification_set_category(ptr str) maa_wine_bridge_notify_notification_set_category
|
||||
@ cdecl notify_notification_set_hint(ptr str ptr) maa_wine_bridge_notify_notification_set_hint
|
||||
@ cdecl notify_notification_set_image_from_pixbuf(ptr ptr) maa_wine_bridge_notify_notification_set_image_from_pixbuf
|
||||
@ cdecl notify_notification_set_timeout(ptr long) maa_wine_bridge_notify_notification_set_timeout
|
||||
@ cdecl notify_notification_set_urgency(ptr long) maa_wine_bridge_notify_notification_set_urgency
|
||||
@ cdecl notify_notification_show(ptr ptr) maa_wine_bridge_notify_notification_show
|
||||
@ cdecl notify_notification_update(ptr str str str) maa_wine_bridge_notify_notification_update
|
||||
|
||||
@ cdecl gdk_pixbuf_new_from_data(ptr long long long long long long ptr ptr) maa_wine_bridge_gdk_pixbuf_new_from_data
|
||||
|
||||
@ stdcall glib_default_main_loop_start() maa_wine_bridge_glib_default_main_loop_start
|
||||
@ stdcall glib_default_main_loop_stop() maa_wine_bridge_glib_default_main_loop_stop
|
||||
|
||||
@ cdecl FcInitLoadConfigAndFonts() MaaFcInitLoadConfigAndFonts
|
||||
@ cdecl FcNameParse(str) MaaFcNameParse
|
||||
@ cdecl FcConfigSubstitute(ptr ptr long) MaaFcConfigSubstitute
|
||||
@ cdecl FcDefaultSubstitute(ptr) MaaFcDefaultSubstitute
|
||||
@ cdecl FcFontSort(ptr ptr long ptr ptr) MaaFcFontSort
|
||||
@ cdecl FcCharSetCreate() MaaFcCharSetCreate
|
||||
@ cdecl FcPatternGetString(ptr str long ptr) MaaFcPatternGetString
|
||||
@ cdecl FcPatternGetCharSet(ptr str long ptr) MaaFcPatternGetCharSet
|
||||
@ cdecl FcCharSetSubtract(ptr ptr) MaaFcCharSetSubtract
|
||||
@ cdecl FcCharSetMerge(ptr ptr ptr) MaaFcCharSetMerge
|
||||
@ cdecl FcCharSetFirstPage(ptr ptr ptr) MaaFcCharSetFirstPage
|
||||
@ cdecl FcCharSetNextPage(ptr ptr ptr) MaaFcCharSetNextPage
|
||||
@ cdecl FcCharSetDestroy(ptr) MaaFcCharSetDestroy
|
||||
@ cdecl FcFontSetDestroy(ptr) MaaFcFontSetDestroy
|
||||
@ cdecl FcPatternDestroy(ptr) MaaFcPatternDestroy
|
||||
@ cdecl FcConfigDestroy(ptr) MaaFcConfigDestroy
|
||||
@ cdecl MaaFcCharsetMapSize()
|
||||
87
src/MaaWineBridge/MaaDesktopIntegration/fontconfig.c
Normal file
87
src/MaaWineBridge/MaaDesktopIntegration/fontconfig.c
Normal file
@@ -0,0 +1,87 @@
|
||||
#include <fontconfig/fontconfig.h>
|
||||
|
||||
#include <windef.h>
|
||||
|
||||
FcConfig* __cdecl MaaFcInitLoadConfigAndFonts()
|
||||
{
|
||||
return FcInitLoadConfigAndFonts();
|
||||
}
|
||||
|
||||
FcPattern* __cdecl MaaFcNameParse(FcChar8* pattern)
|
||||
{
|
||||
return FcNameParse(pattern);
|
||||
}
|
||||
|
||||
FcBool __cdecl MaaFcConfigSubstitute(FcConfig* config, FcPattern* p, FcMatchKind kind)
|
||||
{
|
||||
return FcConfigSubstitute(config, p, kind);
|
||||
}
|
||||
|
||||
void __cdecl MaaFcDefaultSubstitute(FcPattern* p)
|
||||
{
|
||||
FcDefaultSubstitute(p);
|
||||
}
|
||||
|
||||
FcResult __cdecl MaaFcPatternGetString(FcPattern* p, const char* object, int n, FcChar8** s)
|
||||
{
|
||||
return FcPatternGetString(p, object, n, s);
|
||||
}
|
||||
|
||||
FcResult __cdecl MaaFcPatternGetCharSet(FcPattern* p, const char* object, int n, FcCharSet** c)
|
||||
{
|
||||
return FcPatternGetCharSet(p, object, n, c);
|
||||
}
|
||||
|
||||
FcFontSet* __cdecl MaaFcFontSort(FcConfig* config, FcPattern* p, FcBool trim, FcCharSet** csp, FcResult* result)
|
||||
{
|
||||
return FcFontSort(config, p, trim, csp, result);
|
||||
}
|
||||
|
||||
FcCharSet* __cdecl MaaFcCharSetCreate()
|
||||
{
|
||||
return FcCharSetCreate();
|
||||
}
|
||||
|
||||
FcCharSet* __cdecl MaaFcCharSetSubtract(FcCharSet* a, FcCharSet* b)
|
||||
{
|
||||
return FcCharSetSubtract(a, b);
|
||||
}
|
||||
|
||||
FcBool __cdecl MaaFcCharSetMerge(FcCharSet* a, FcCharSet* b, FcBool* c)
|
||||
{
|
||||
return FcCharSetMerge(a, b, c);
|
||||
}
|
||||
|
||||
FcChar32 __cdecl MaaFcCharSetFirstPage(FcCharSet* a, FcChar32* pagemap, FcChar32* len)
|
||||
{
|
||||
return FcCharSetFirstPage(a, pagemap, len);
|
||||
}
|
||||
|
||||
FcChar32 __cdecl MaaFcCharSetNextPage(FcCharSet* a, FcChar32* next, FcChar32* len)
|
||||
{
|
||||
return FcCharSetNextPage(a, next, len);
|
||||
}
|
||||
|
||||
void __cdecl MaaFcCharSetDestroy(FcCharSet* a)
|
||||
{
|
||||
FcCharSetDestroy(a);
|
||||
}
|
||||
|
||||
void __cdecl MaaFcPatternDestroy(FcPattern* p)
|
||||
{
|
||||
FcPatternDestroy(p);
|
||||
}
|
||||
|
||||
void __cdecl MaaFcConfigDestroy(FcConfig* config)
|
||||
{
|
||||
FcConfigDestroy(config);
|
||||
}
|
||||
|
||||
void __cdecl MaaFcFontSetDestroy(FcFontSet* fs)
|
||||
{
|
||||
FcFontSetDestroy(fs);
|
||||
}
|
||||
|
||||
int __cdecl MaaFcCharsetMapSize() {
|
||||
return FC_CHARSET_MAP_SIZE;
|
||||
}
|
||||
48
src/MaaWineBridge/MaaDesktopIntegration/gdk-pixbuf.c
Normal file
48
src/MaaWineBridge/MaaDesktopIntegration/gdk-pixbuf.c
Normal file
@@ -0,0 +1,48 @@
|
||||
#include <gdk-pixbuf/gdk-pixbuf.h>
|
||||
#include "main_loop.h"
|
||||
|
||||
#include <windef.h>
|
||||
|
||||
typedef void(__stdcall* GdkPixbufDestroyNotifyMsAbi)(guchar* pixels, gpointer data);
|
||||
|
||||
struct PixBufDestroyCallbackState
|
||||
{
|
||||
GdkPixbufDestroyNotifyMsAbi free_func;
|
||||
gpointer user_data;
|
||||
};
|
||||
|
||||
static void pixbuf_destroy_callback(guchar* pixels, gpointer data)
|
||||
{
|
||||
struct PixBufDestroyCallbackState* state = (struct PixBufDestroyCallbackState*)data;
|
||||
// not guaranteed to be called from the main loop
|
||||
invoke_in_main_loop_stdcall(state->free_func, 2, pixels, state->user_data);
|
||||
free(state);
|
||||
}
|
||||
|
||||
GdkPixbuf* __cdecl maa_wine_bridge_gdk_pixbuf_new_from_data(
|
||||
const guchar* data,
|
||||
GdkColorspace colorspace,
|
||||
gboolean has_alpha,
|
||||
int bits_per_sample,
|
||||
int width,
|
||||
int height,
|
||||
int rowstride,
|
||||
GdkPixbufDestroyNotifyMsAbi destroy_fn,
|
||||
gpointer destroy_fn_data)
|
||||
{
|
||||
struct PixBufDestroyCallbackState* state = malloc(sizeof(struct PixBufDestroyCallbackState));
|
||||
state->free_func = destroy_fn;
|
||||
state->user_data = destroy_fn_data;
|
||||
|
||||
return gdk_pixbuf_new_from_data(
|
||||
data,
|
||||
colorspace,
|
||||
has_alpha,
|
||||
bits_per_sample,
|
||||
width,
|
||||
height,
|
||||
rowstride,
|
||||
pixbuf_destroy_callback,
|
||||
state);
|
||||
}
|
||||
|
||||
58
src/MaaWineBridge/MaaDesktopIntegration/gobject.c
Normal file
58
src/MaaWineBridge/MaaDesktopIntegration/gobject.c
Normal file
@@ -0,0 +1,58 @@
|
||||
#include <glib.h>
|
||||
#include <glib-object.h>
|
||||
|
||||
#include "main_loop.h"
|
||||
|
||||
#include <windef.h>
|
||||
|
||||
void __cdecl maa_wine_bridge_g_object_unref(void* object)
|
||||
{
|
||||
g_object_unref(G_OBJECT(object));
|
||||
}
|
||||
|
||||
typedef void(__cdecl* GClosureNotifyMsAbi)(gpointer data, GClosure* closure);
|
||||
|
||||
typedef void(__cdecl* GCallbackMsAbi)(gpointer data, gpointer data2);
|
||||
|
||||
typedef struct _shim_g_signal_callback_state
|
||||
{
|
||||
GCallbackMsAbi c_handler;
|
||||
gpointer data;
|
||||
GClosureNotifyMsAbi destroy_data;
|
||||
} shim_g_signal_callback_state;
|
||||
|
||||
static void shim_g_signal_callback(gpointer sender, gpointer arg)
|
||||
{
|
||||
shim_g_signal_callback_state* state = (shim_g_signal_callback_state*)arg;
|
||||
// called from the main loop (wine-aware thread)
|
||||
state->c_handler(sender, state->data);
|
||||
}
|
||||
|
||||
static void shim_g_signal_callback_destroy(gpointer data, GClosure* closure)
|
||||
{
|
||||
shim_g_signal_callback_state* state = (shim_g_signal_callback_state*)data;
|
||||
// called from the main loop (wine-aware thread)
|
||||
state->destroy_data(state->data, closure);
|
||||
free(state);
|
||||
}
|
||||
|
||||
gulong __cdecl maa_wine_bridge_g_signal_connect_data(
|
||||
void* instance,
|
||||
const char* detailed_signal,
|
||||
GCallbackMsAbi c_handler,
|
||||
gpointer data,
|
||||
GClosureNotifyMsAbi destroy_data,
|
||||
GConnectFlags connect_flags)
|
||||
{
|
||||
shim_g_signal_callback_state* state = malloc(sizeof(shim_g_signal_callback_state));
|
||||
state->c_handler = c_handler;
|
||||
state->data = data;
|
||||
state->destroy_data = destroy_data;
|
||||
return g_signal_connect_data(
|
||||
instance,
|
||||
detailed_signal,
|
||||
G_CALLBACK(shim_g_signal_callback),
|
||||
state,
|
||||
shim_g_signal_callback_destroy,
|
||||
connect_flags);
|
||||
}
|
||||
108
src/MaaWineBridge/MaaDesktopIntegration/main_loop.c
Normal file
108
src/MaaWineBridge/MaaDesktopIntegration/main_loop.c
Normal file
@@ -0,0 +1,108 @@
|
||||
#include <glib.h>
|
||||
#include <stdint.h>
|
||||
// clang-format off
|
||||
#include <windef.h>
|
||||
#include <winbase.h>
|
||||
// clang-format on
|
||||
#include "main_loop.h"
|
||||
|
||||
typedef struct _pending_call
|
||||
{
|
||||
void* function;
|
||||
int arg_count;
|
||||
uintptr_t args[CALLBACK_FORWARDER_MAX_ARGS];
|
||||
uintptr_t result;
|
||||
} pending_call;
|
||||
|
||||
typedef uintptr_t(__stdcall* Callback0)();
|
||||
typedef uintptr_t(__stdcall* Callback1)(uintptr_t);
|
||||
typedef uintptr_t(__stdcall* Callback2)(uintptr_t, uintptr_t);
|
||||
typedef uintptr_t(__stdcall* Callback3)(uintptr_t, uintptr_t, uintptr_t);
|
||||
typedef uintptr_t(__stdcall* Callback4)(uintptr_t, uintptr_t, uintptr_t, uintptr_t);
|
||||
|
||||
static GMainLoop* main_loop;
|
||||
static GMainContext* main_context;
|
||||
|
||||
static int WINAPI gmainloop_thread(void* arg)
|
||||
{
|
||||
GMainLoop* loop = arg;
|
||||
g_main_loop_ref(loop);
|
||||
g_main_loop_run(loop);
|
||||
g_main_loop_unref(loop);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __cdecl maa_wine_bridge_glib_default_main_loop_start()
|
||||
{
|
||||
if (main_loop != NULL) {
|
||||
return 0;
|
||||
}
|
||||
main_context = g_main_context_default();
|
||||
GMainLoop* loop = g_main_loop_new(main_context, FALSE);
|
||||
main_loop = loop;
|
||||
HANDLE thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)gmainloop_thread, loop, 0, NULL);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int __cdecl maa_wine_bridge_glib_default_main_loop_stop()
|
||||
{
|
||||
if (!main_loop) {
|
||||
return 0;
|
||||
}
|
||||
GMainLoop* loop = main_loop;
|
||||
main_loop = NULL;
|
||||
g_main_loop_quit(loop);
|
||||
g_main_loop_unref(loop);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static gboolean invoke_helper(gpointer lparam)
|
||||
{
|
||||
pending_call* pending = (pending_call*)lparam;
|
||||
switch (pending->arg_count) {
|
||||
case 0:
|
||||
pending->result = ((Callback0)pending->function)();
|
||||
break;
|
||||
case 1:
|
||||
pending->result = ((Callback1)pending->function)(pending->args[0]);
|
||||
break;
|
||||
case 2:
|
||||
pending->result = ((Callback2)pending->function)(pending->args[0], pending->args[1]);
|
||||
break;
|
||||
case 3:
|
||||
pending->result =
|
||||
((Callback3)pending->function)(pending->args[0], pending->args[1], pending->args[2]);
|
||||
break;
|
||||
case 4:
|
||||
pending->result = ((Callback4)pending->function)(
|
||||
pending->args[0],
|
||||
pending->args[1],
|
||||
pending->args[2],
|
||||
pending->args[3]);
|
||||
break;
|
||||
}
|
||||
free(pending);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void invoke_in_main_loop_stdcall(void* callback, int arg_count, ...)
|
||||
{
|
||||
if (arg_count > CALLBACK_FORWARDER_MAX_ARGS) {
|
||||
abort();
|
||||
}
|
||||
pending_call* pending = malloc(sizeof(pending_call));
|
||||
pending->function = callback;
|
||||
pending->arg_count = arg_count;
|
||||
va_list args;
|
||||
va_start(args, arg_count);
|
||||
for (int i = 0; i < arg_count; i++) {
|
||||
pending->args[i] = va_arg(args, uintptr_t);
|
||||
}
|
||||
va_end(args);
|
||||
g_main_context_invoke(main_context, invoke_helper, pending);
|
||||
}
|
||||
|
||||
void invoke_in_main_loop(GSourceFunc callback, void* arg)
|
||||
{
|
||||
g_main_context_invoke(main_context, callback, arg);
|
||||
}
|
||||
10
src/MaaWineBridge/MaaDesktopIntegration/main_loop.h
Normal file
10
src/MaaWineBridge/MaaDesktopIntegration/main_loop.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <windef.h>
|
||||
|
||||
#define CALLBACK_FORWARDER_MAX_ARGS 4
|
||||
|
||||
int __stdcall maa_wine_bridge_main_loop_start();
|
||||
int __stdcall maa_wine_bridge_main_loop_stop();
|
||||
void invoke_in_main_loop_stdcall(void* callback, int arg_count, ...);
|
||||
void invoke_in_main_loop(GSourceFunc callback, void *arg);
|
||||
159
src/MaaWineBridge/MaaDesktopIntegration/notification.c
Normal file
159
src/MaaWineBridge/MaaDesktopIntegration/notification.c
Normal file
@@ -0,0 +1,159 @@
|
||||
#include <libnotify/notify.h>
|
||||
|
||||
#include <windef.h>
|
||||
|
||||
#include "main_loop.h"
|
||||
|
||||
int __cdecl maa_wine_bridge_notify_init(const char* app_name)
|
||||
{
|
||||
return notify_init(app_name);
|
||||
}
|
||||
|
||||
NotifyNotification* __cdecl maa_wine_bridge_notify_notification_new(
|
||||
const char* summary,
|
||||
const char* body,
|
||||
const char* icon)
|
||||
{
|
||||
return notify_notification_new(summary, body, icon);
|
||||
}
|
||||
|
||||
typedef void(__cdecl* NotifyActionCallbackMsAbi)(
|
||||
NotifyNotification* notification,
|
||||
char* action,
|
||||
gpointer user_data);
|
||||
|
||||
typedef void(__cdecl* GFreeFuncMsAbi)(gpointer data);
|
||||
|
||||
struct NotifyActionCallbackState
|
||||
{
|
||||
NotifyActionCallbackMsAbi x64abi_callback;
|
||||
gpointer user_data;
|
||||
GFreeFuncMsAbi free_func;
|
||||
};
|
||||
|
||||
static void maa_wine_bridge_notify_action_callback(
|
||||
NotifyNotification* notification,
|
||||
char* action,
|
||||
gpointer user_data)
|
||||
{
|
||||
struct NotifyActionCallbackState* state = (struct NotifyActionCallbackState*)user_data;
|
||||
// called from the main loop (wine-aware thread)
|
||||
state->x64abi_callback(notification, action, state->user_data);
|
||||
}
|
||||
|
||||
static void unix_free_func(gpointer data)
|
||||
{
|
||||
struct NotifyActionCallbackState* state = (struct NotifyActionCallbackState*)data;
|
||||
state->free_func(state->user_data);
|
||||
free(state);
|
||||
}
|
||||
|
||||
void __cdecl maa_wine_bridge_notify_notification_add_action(
|
||||
NotifyNotification* notification,
|
||||
const char* action,
|
||||
const char* label,
|
||||
NotifyActionCallbackMsAbi callback,
|
||||
gpointer user_data,
|
||||
GFreeFuncMsAbi free_func)
|
||||
{
|
||||
struct NotifyActionCallbackState* state = malloc(sizeof(struct NotifyActionCallbackState));
|
||||
state->x64abi_callback = callback;
|
||||
state->user_data = user_data;
|
||||
state->free_func = free_func;
|
||||
notify_notification_add_action(
|
||||
notification,
|
||||
action,
|
||||
label,
|
||||
maa_wine_bridge_notify_action_callback,
|
||||
state,
|
||||
unix_free_func);
|
||||
}
|
||||
|
||||
void __cdecl maa_wine_bridge_notify_notification_clear_actions(NotifyNotification* notification)
|
||||
{
|
||||
notify_notification_clear_actions(notification);
|
||||
}
|
||||
|
||||
void __cdecl maa_wine_bridge_notify_notification_clear_hints(NotifyNotification* notification)
|
||||
{
|
||||
notify_notification_clear_hints(notification);
|
||||
}
|
||||
|
||||
void __cdecl maa_wine_bridge_notify_notification_close(
|
||||
NotifyNotification* notification,
|
||||
GError** error)
|
||||
{
|
||||
notify_notification_close(notification, error);
|
||||
}
|
||||
|
||||
const char* __cdecl maa_wine_bridge_notify_notification_get_activation_token(
|
||||
NotifyNotification* notification)
|
||||
{
|
||||
return notify_notification_get_activation_token(notification);
|
||||
}
|
||||
|
||||
gint __cdecl maa_wine_bridge_notify_notification_get_closed_reason(
|
||||
const NotifyNotification* notification)
|
||||
{
|
||||
return notify_notification_get_closed_reason(notification);
|
||||
}
|
||||
|
||||
void __cdecl maa_wine_bridge_notify_notification_set_app_name(
|
||||
NotifyNotification* notification,
|
||||
const char* app_name)
|
||||
{
|
||||
notify_notification_set_app_name(notification, app_name);
|
||||
}
|
||||
|
||||
void __cdecl maa_wine_bridge_notify_notification_set_category(
|
||||
NotifyNotification* notification,
|
||||
const char* category)
|
||||
{
|
||||
notify_notification_set_category(notification, category);
|
||||
}
|
||||
|
||||
void __cdecl maa_wine_bridge_notify_notification_set_hint(
|
||||
NotifyNotification* notification,
|
||||
const char* key,
|
||||
GVariant* value)
|
||||
{
|
||||
notify_notification_set_hint(notification, key, value);
|
||||
}
|
||||
|
||||
void __cdecl maa_wine_bridge_notify_notification_set_image_from_pixbuf(
|
||||
NotifyNotification* notification,
|
||||
GdkPixbuf* pixbuf)
|
||||
{
|
||||
notify_notification_set_image_from_pixbuf(notification, pixbuf);
|
||||
}
|
||||
|
||||
void __cdecl maa_wine_bridge_notify_notification_set_timeout(
|
||||
NotifyNotification* notification,
|
||||
gint timeout)
|
||||
{
|
||||
notify_notification_set_timeout(notification, timeout);
|
||||
}
|
||||
|
||||
void __cdecl maa_wine_bridge_notify_notification_set_urgency(
|
||||
NotifyNotification* notification,
|
||||
NotifyUrgency urgency)
|
||||
{
|
||||
notify_notification_set_urgency(notification, urgency);
|
||||
}
|
||||
|
||||
void __cdecl maa_wine_bridge_notify_notification_show(
|
||||
NotifyNotification* notification,
|
||||
GError** error)
|
||||
{
|
||||
notify_notification_show(notification, error);
|
||||
}
|
||||
|
||||
gboolean __cdecl maa_wine_bridge_notify_notification_update(
|
||||
NotifyNotification* notification,
|
||||
const char* summary,
|
||||
const char* body,
|
||||
const char* icon)
|
||||
{
|
||||
return notify_notification_update(notification, summary, body, icon);
|
||||
}
|
||||
|
||||
11
src/MaaWineBridge/README.md
Normal file
11
src/MaaWineBridge/README.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# MAA Wine Bridge
|
||||
|
||||
## Getting Started
|
||||
|
||||
```console
|
||||
$ cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE=winegcc.cmake
|
||||
$ cmake --build build
|
||||
$ cmake --install build --prefix build
|
||||
```
|
||||
|
||||
Place `build/MaaCore.dll` next to `MAA.exe`. When is gets loaded by Wine, it will load `libMaaCore.so` from the same directory and forward calls to it.
|
||||
7
src/MaaWineBridge/winegcc.cmake
Normal file
7
src/MaaWineBridge/winegcc.cmake
Normal file
@@ -0,0 +1,7 @@
|
||||
# CMake behaves differently when CMAKE_SYSTEM_NAME is set
|
||||
# set(CMAKE_SYSTEM_NAME ${CMAKE_HOST_SYSTEM_NAME})
|
||||
# set(CMAKE_SYSTEM_PROCESSOR ${CMAKE_HOST_SYSTEM_PROCESSOR})
|
||||
set(CMAKE_C_COMPILER winegcc)
|
||||
set(CMAKE_CXX_COMPILER wineg++)
|
||||
set(CMAKE_C_STANDARD_INCLUDE_DIRECTORIES /usr/include/wine/windows)
|
||||
set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES /usr/include/wine/windows)
|
||||
@@ -20,6 +20,7 @@
|
||||
<main:Bootstrapper />
|
||||
</s:ApplicationLoader.Bootstrapper>
|
||||
</s:ApplicationLoader>
|
||||
<ResourceDictionary Source="pack://application:,,,/PresentationFramework.Aero2;component/themes/Aero2.NormalColor.xaml" />
|
||||
<ResourceDictionary Source="/MAA;component/Res/Theme.xaml" />
|
||||
<ResourceDictionary Source="/MAA;component/Res/Style.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
@@ -18,6 +18,8 @@ using System.Windows;
|
||||
using System.Windows.Documents;
|
||||
using MaaWpfGui.Helper;
|
||||
using MaaWpfGui.Main;
|
||||
using MaaWpfGui.WineCompat;
|
||||
using MaaWpfGui.WineCompat.FontConfig;
|
||||
using Serilog;
|
||||
|
||||
namespace MaaWpfGui
|
||||
@@ -44,6 +46,12 @@ namespace MaaWpfGui
|
||||
|
||||
protected override void OnStartup(StartupEventArgs e)
|
||||
{
|
||||
if (WineRuntimeInformation.IsRunningUnderWine && MaaDesktopIntegration.Availabile)
|
||||
{
|
||||
// override buintin font map as early as possible
|
||||
FontConfigIntegration.Install();
|
||||
}
|
||||
|
||||
base.OnStartup(e);
|
||||
|
||||
string[] args = e.Args;
|
||||
|
||||
51
src/MaaWpfGui/Helper/AppIcon.cs
Normal file
51
src/MaaWpfGui/Helper/AppIcon.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
// <copyright file="AppIcon.cs" company="MaaAssistantArknights">
|
||||
// MaaWpfGui - A part of the MaaCoreArknights project
|
||||
// Copyright (C) 2021 MistEO and Contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY
|
||||
// </copyright>
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace MaaWpfGui.Helper;
|
||||
|
||||
public class AppIcon
|
||||
{
|
||||
private static readonly Lazy<BitmapSource> lazyIcon = new(ExtractIcon);
|
||||
|
||||
private static BitmapSource ExtractIcon()
|
||||
{
|
||||
try
|
||||
{
|
||||
var sri = Application.GetResourceStream(new Uri("pack://application:,,,/newlogo.ico"));
|
||||
if (sri == null)
|
||||
{
|
||||
return new BitmapImage();
|
||||
}
|
||||
|
||||
using var s = sri.Stream;
|
||||
var decoder = BitmapDecoder.Create(s, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);
|
||||
var imageSource = decoder.Frames[0];
|
||||
|
||||
return imageSource;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
return new BitmapImage();
|
||||
}
|
||||
}
|
||||
|
||||
public static BitmapSource GetIcon() => lazyIcon.Value;
|
||||
}
|
||||
23
src/MaaWpfGui/Helper/Notification/INotificationPoster.cs
Normal file
23
src/MaaWpfGui/Helper/Notification/INotificationPoster.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
// <copyright file="INotificationPoster.cs" company="MaaAssistantArknights">
|
||||
// MaaWpfGui - A part of the MaaCoreArknights project
|
||||
// Copyright (C) 2021 MistEO and Contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY
|
||||
// </copyright>
|
||||
|
||||
using System;
|
||||
|
||||
namespace MaaWpfGui.Helper.Notification;
|
||||
|
||||
internal interface INotificationPoster
|
||||
{
|
||||
void ShowNotification(NotificationContent content);
|
||||
|
||||
event EventHandler<string> ActionActivated;
|
||||
}
|
||||
16
src/MaaWpfGui/Helper/Notification/NotificationAction.cs
Normal file
16
src/MaaWpfGui/Helper/Notification/NotificationAction.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
// <copyright file="NotificationAction.cs" company="MaaAssistantArknights">
|
||||
// MaaWpfGui - A part of the MaaCoreArknights project
|
||||
// Copyright (C) 2021 MistEO and Contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY
|
||||
// </copyright>
|
||||
|
||||
namespace MaaWpfGui.Helper.Notification;
|
||||
|
||||
internal record class NotificationAction(string Label, string Tag);
|
||||
31
src/MaaWpfGui/Helper/Notification/NotificationContent.cs
Normal file
31
src/MaaWpfGui/Helper/Notification/NotificationContent.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
// <copyright file="NotificationContent.cs" company="MaaAssistantArknights">
|
||||
// MaaWpfGui - A part of the MaaCoreArknights project
|
||||
// Copyright (C) 2021 MistEO and Contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY
|
||||
// </copyright>
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MaaWpfGui.Helper.Notification;
|
||||
|
||||
internal class NotificationContent
|
||||
{
|
||||
public string Summary { get; set; }
|
||||
|
||||
public string Body { get; set; }
|
||||
|
||||
private List<NotificationAction> _actions = new List<NotificationAction>();
|
||||
|
||||
public IList<NotificationAction> Actions => _actions;
|
||||
|
||||
private List<NotificationHint> _hints = new List<NotificationHint>();
|
||||
|
||||
public IList<NotificationHint> Hints => _hints;
|
||||
}
|
||||
52
src/MaaWpfGui/Helper/Notification/NotificationHint.cs
Normal file
52
src/MaaWpfGui/Helper/Notification/NotificationHint.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
// <copyright file="NotificationHint.cs" company="MaaAssistantArknights">
|
||||
// MaaWpfGui - A part of the MaaCoreArknights project
|
||||
// Copyright (C) 2021 MistEO and Contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY
|
||||
// </copyright>
|
||||
|
||||
using System;
|
||||
|
||||
namespace MaaWpfGui.Helper.Notification;
|
||||
|
||||
public class NotificationHint
|
||||
{
|
||||
public static NotificationHint Expandable { get; } = new NotificationHint();
|
||||
|
||||
public static NotificationHint RecruitHighRarity { get; } = new NotificationHint();
|
||||
|
||||
public static NotificationHint RecruitRobot { get; } = new NotificationHint();
|
||||
|
||||
public static NotificationHint NewVersion { get; } = new NotificationHint();
|
||||
|
||||
internal class NotificationHintRowCount : NotificationHint
|
||||
{
|
||||
public int Value { get; }
|
||||
|
||||
public NotificationHintRowCount(int rowCount)
|
||||
{
|
||||
Value = rowCount;
|
||||
}
|
||||
}
|
||||
|
||||
internal class NotificationHintExpirationTime : NotificationHint
|
||||
{
|
||||
public TimeSpan Value { get; }
|
||||
|
||||
public NotificationHintExpirationTime(TimeSpan value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public static NotificationHint RowCount(int rowCount) => new NotificationHintRowCount(rowCount);
|
||||
|
||||
public static NotificationHint ExpirationTime(TimeSpan value) => new NotificationHintExpirationTime(value);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// <copyright file="NotificationImplLibNotify.cs" company="MaaAssistantArknights">
|
||||
// MaaWpfGui - A part of the MaaCoreArknights project
|
||||
// Copyright (C) 2021 MistEO and Contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY
|
||||
// </copyright>
|
||||
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using MaaWpfGui.WineCompat;
|
||||
|
||||
namespace MaaWpfGui.Helper.Notification;
|
||||
|
||||
internal class NotificationImplLibNotify : INotificationPoster
|
||||
{
|
||||
public event EventHandler<string> ActionActivated;
|
||||
|
||||
public static bool IsSupported { get; } = CheckSupport();
|
||||
|
||||
private static GdkPixBuf _icon;
|
||||
|
||||
private static bool CheckSupport()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (MaaDesktopIntegration.notify_init("MAA") == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
var bmp3 = new FormatConvertedBitmap(AppIcon.GetIcon(), PixelFormats.Bgra32, null, 0);
|
||||
var pixels = new byte[bmp3.PixelWidth * bmp3.PixelHeight * 4];
|
||||
bmp3.CopyPixels(pixels, bmp3.PixelWidth * 4, 0);
|
||||
|
||||
// BGRA -> RGBA
|
||||
var u32view = MemoryMarshal.Cast<byte, uint>(pixels.AsSpan());
|
||||
for (int i = 0; i < u32view.Length; i++)
|
||||
{
|
||||
var bgra = u32view[i]; // BB GG RR AA -> 0xAARRGGBB
|
||||
var argb = BinaryPrimitives.ReverseEndianness(bgra); // AA RR GG BB 0xBBGGRRAA
|
||||
var rgba = BitOperations.RotateRight(argb, 8); // RR GG BB AA -> 0xAABBGGRR
|
||||
u32view[i] = rgba;
|
||||
}
|
||||
|
||||
_icon = GdkPixBuf.CreateFromPixels(bmp3.PixelWidth, bmp3.PixelHeight, bmp3.PixelWidth * 4, true, pixels);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ShowNotification(NotificationContent content)
|
||||
{
|
||||
var notification = new LibNotifyNotification(content.Summary, content.Body, "");
|
||||
notification.SetImageFromPixbuf(_icon);
|
||||
foreach (var action in content.Actions)
|
||||
{
|
||||
notification.AddAction(action.Tag, action.Label);
|
||||
}
|
||||
|
||||
notification.ActionActivated += (sender, args) => ActionActivated?.Invoke(this, args);
|
||||
notification.Show();
|
||||
var gch = GCHandle.Alloc(notification);
|
||||
notification.Closed += (sender, args) =>
|
||||
{
|
||||
gch.Free();
|
||||
};
|
||||
}
|
||||
}
|
||||
52
src/MaaWpfGui/Helper/Notification/NotificationImplWinRT.cs
Normal file
52
src/MaaWpfGui/Helper/Notification/NotificationImplWinRT.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
// <copyright file="NotificationImplWinRT.cs" company="MaaAssistantArknights">
|
||||
// MaaWpfGui - A part of the MaaCoreArknights project
|
||||
// Copyright (C) 2021 MistEO and Contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY
|
||||
// </copyright>
|
||||
|
||||
using System;
|
||||
using Microsoft.Toolkit.Uwp.Notifications;
|
||||
|
||||
namespace MaaWpfGui.Helper.Notification;
|
||||
|
||||
internal class NotificationImplWinRT : INotificationPoster, IDisposable
|
||||
{
|
||||
public event EventHandler<string> ActionActivated;
|
||||
|
||||
public NotificationImplWinRT()
|
||||
{
|
||||
ToastNotificationManagerCompat.OnActivated += OnWinRTActivated;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
ToastNotificationManagerCompat.OnActivated -= OnWinRTActivated;
|
||||
ToastNotificationManagerCompat.History.Clear();
|
||||
}
|
||||
|
||||
private void OnWinRTActivated(ToastNotificationActivatedEventArgsCompat args)
|
||||
{
|
||||
ActionActivated?.Invoke(this, args.Argument);
|
||||
}
|
||||
|
||||
public void ShowNotification(NotificationContent content)
|
||||
{
|
||||
var builder = new ToastContentBuilder().AddText(content.Body).AddText(content.Summary);
|
||||
|
||||
foreach (var action in content.Actions)
|
||||
{
|
||||
builder.AddButton(new ToastButton()
|
||||
.SetContent(action.Label)
|
||||
.AddArgument(action.Tag));
|
||||
}
|
||||
|
||||
builder.Show();
|
||||
}
|
||||
}
|
||||
150
src/MaaWpfGui/Helper/Notification/NotificationImplWpf.cs
Normal file
150
src/MaaWpfGui/Helper/Notification/NotificationImplWpf.cs
Normal file
@@ -0,0 +1,150 @@
|
||||
// <copyright file="NotificationImplWpf.cs" company="MaaAssistantArknights">
|
||||
// MaaWpfGui - A part of the MaaCoreArknights project
|
||||
// Copyright (C) 2021 MistEO and Contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY
|
||||
// </copyright>
|
||||
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using Notification.Wpf;
|
||||
using Notification.Wpf.Base;
|
||||
using Notification.Wpf.Constants;
|
||||
using Notification.Wpf.Controls;
|
||||
|
||||
namespace MaaWpfGui.Helper.Notification;
|
||||
|
||||
internal class NotificationImplWpf : INotificationPoster
|
||||
{
|
||||
private NotificationManager _notificationManager = new NotificationManager();
|
||||
private BrushConverter _brushConverter = new BrushConverter();
|
||||
|
||||
public event EventHandler<string> ActionActivated;
|
||||
|
||||
public NotificationImplWpf()
|
||||
{
|
||||
// 同时显示最大数量
|
||||
NotificationConstants.NotificationsOverlayWindowMaxCount = 5;
|
||||
|
||||
// 默认显示位置
|
||||
NotificationConstants.MessagePosition = NotificationPosition.BottomRight;
|
||||
|
||||
// 最小显示宽度
|
||||
NotificationConstants.MinWidth = 400d;
|
||||
|
||||
// 最大显示宽度
|
||||
NotificationConstants.MaxWidth = 460d;
|
||||
}
|
||||
|
||||
#region 通知基本字体样式和内容模板
|
||||
|
||||
/// <summary>
|
||||
/// Gets basic text styles.
|
||||
/// </summary>
|
||||
/// <remarks>创建一个基本文本字体样式。</remarks>
|
||||
public TextContentSettings BaseTextSettings => new TextContentSettings()
|
||||
{
|
||||
FontStyle = FontStyles.Normal,
|
||||
FontFamily = new FontFamily("Microsoft Yahei"),
|
||||
FontSize = 14,
|
||||
FontWeight = FontWeights.Normal,
|
||||
TextAlignment = TextAlignment.Left,
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch,
|
||||
VerticalTextAlignment = VerticalAlignment.Stretch,
|
||||
Opacity = 1d,
|
||||
};
|
||||
|
||||
#endregion 通知基本字体样式和内容模板
|
||||
|
||||
|
||||
public void ShowNotification(NotificationContent content)
|
||||
{
|
||||
var wpfcontent = new global::Notification.Wpf.NotificationContent()
|
||||
{
|
||||
Title = content.Summary,
|
||||
Message = content.Body,
|
||||
|
||||
Type = NotificationType.None,
|
||||
Icon = AppIcon.GetIcon(),
|
||||
CloseOnClick = true,
|
||||
|
||||
RowsCount = 1,
|
||||
TrimType = NotificationTextTrimType.AttachIfMoreRows,
|
||||
|
||||
Background = (SolidColorBrush)new BrushConverter().ConvertFrom("#FF1F3550"),
|
||||
};
|
||||
|
||||
// 默认的标题文本样式
|
||||
var titleTextSettings = BaseTextSettings;
|
||||
titleTextSettings.FontSize = 18d;
|
||||
wpfcontent.TitleTextSettings = titleTextSettings;
|
||||
|
||||
// 默认的内容文本样式
|
||||
var messageTextSettings = BaseTextSettings;
|
||||
wpfcontent.MessageTextSettings = messageTextSettings;
|
||||
|
||||
var expirationTime = TimeSpan.FromSeconds(3);
|
||||
|
||||
foreach (var hint in content.Hints)
|
||||
{
|
||||
if (hint == NotificationHint.Expandable)
|
||||
{
|
||||
wpfcontent.TrimType = NotificationTextTrimType.Attach;
|
||||
}
|
||||
else if (hint == NotificationHint.RecruitHighRarity)
|
||||
{
|
||||
wpfcontent.Background = (SolidColorBrush)_brushConverter.ConvertFrom("#FF401804");
|
||||
wpfcontent.Foreground = (SolidColorBrush)_brushConverter.ConvertFrom("#FFFFC800");
|
||||
}
|
||||
else if (hint == NotificationHint.RecruitRobot)
|
||||
{
|
||||
// 给通知染上小车相似的颜色
|
||||
wpfcontent.Background = (SolidColorBrush)_brushConverter.ConvertFrom("#FFFFFFF4");
|
||||
wpfcontent.Foreground = (SolidColorBrush)_brushConverter.ConvertFrom("#FF111111");
|
||||
}
|
||||
else if (hint == NotificationHint.NewVersion)
|
||||
{
|
||||
wpfcontent.Background = (SolidColorBrush)_brushConverter.ConvertFrom("#FF007280");
|
||||
}
|
||||
else if (hint is NotificationHint.NotificationHintRowCount rowCount)
|
||||
{
|
||||
wpfcontent.RowsCount = (uint)rowCount.Value;
|
||||
}
|
||||
else if (hint is NotificationHint.NotificationHintExpirationTime expirationTimeHint)
|
||||
{
|
||||
expirationTime = expirationTimeHint.Value;
|
||||
}
|
||||
}
|
||||
|
||||
if (content.Actions.Count >= 1)
|
||||
{
|
||||
var action = content.Actions[0];
|
||||
wpfcontent.LeftButtonContent = action.Label;
|
||||
wpfcontent.LeftButtonAction = () => ActionActivated?.Invoke(this, action.Tag);
|
||||
if (expirationTime < TimeSpan.FromSeconds(10))
|
||||
{
|
||||
expirationTime = TimeSpan.FromSeconds(10);
|
||||
}
|
||||
}
|
||||
|
||||
if (content.Actions.Count >= 2)
|
||||
{
|
||||
var action = content.Actions[1];
|
||||
wpfcontent.RightButtonContent = action.Label;
|
||||
wpfcontent.RightButtonAction = () => ActionActivated?.Invoke(this, action.Tag);
|
||||
}
|
||||
|
||||
_notificationManager.Show(wpfcontent, expirationTime: expirationTime, ShowXbtn: false);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,6 +16,7 @@ using System.Windows.Media;
|
||||
using HandyControl.Themes;
|
||||
using HandyControl.Tools;
|
||||
using MaaWpfGui.Constants;
|
||||
using MaaWpfGui.WineCompat;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace MaaWpfGui.Helper
|
||||
@@ -27,7 +28,12 @@ namespace MaaWpfGui.Helper
|
||||
private static void SystemEvents_UserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e)
|
||||
{
|
||||
ThemeManager.Current.ApplicationTheme = ThemeManager.GetSystemTheme(isSystemTheme: false);
|
||||
ThemeManager.Current.AccentColor = ThemeManager.Current.GetAccentColorFromSystem();
|
||||
|
||||
if (!WineRuntimeInformation.IsRunningUnderWine)
|
||||
{
|
||||
ThemeManager.Current.AccentColor = ThemeManager.Current.GetAccentColorFromSystem();
|
||||
}
|
||||
|
||||
Application.Current.Resources["TitleBrush"] = ThemeManager.Current.AccentColor;
|
||||
}
|
||||
|
||||
@@ -49,9 +55,17 @@ namespace MaaWpfGui.Helper
|
||||
|
||||
public static void SwitchToSyncWithOsMode()
|
||||
{
|
||||
ThemeManager.Current.UsingWindowsAppTheme = true;
|
||||
if (WineRuntimeInformation.IsRunningUnderWine)
|
||||
{
|
||||
ThemeManager.Current.ApplicationTheme = ThemeManager.GetSystemTheme(isSystemTheme: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
ThemeManager.Current.UsingWindowsAppTheme = true;
|
||||
SystemEvents.UserPreferenceChanged += SystemEvents_UserPreferenceChanged;
|
||||
}
|
||||
|
||||
Application.Current.Resources["TitleBrush"] = ThemeManager.Current.AccentColor;
|
||||
SystemEvents.UserPreferenceChanged += SystemEvents_UserPreferenceChanged;
|
||||
}
|
||||
|
||||
#endregion Swith Theme
|
||||
|
||||
@@ -13,30 +13,20 @@
|
||||
#pragma warning disable SA1307
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Drawing;
|
||||
using System.Media;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Windows;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using HandyControl.Data;
|
||||
using MaaWpfGui.Configuration;
|
||||
using Microsoft.Toolkit.Uwp.Notifications;
|
||||
using MaaWpfGui.Helper.Notification;
|
||||
using MaaWpfGui.WineCompat;
|
||||
using Microsoft.Win32;
|
||||
using Notification.Wpf;
|
||||
using Notification.Wpf.Base;
|
||||
using Notification.Wpf.Constants;
|
||||
using Notification.Wpf.Controls;
|
||||
using Semver;
|
||||
|
||||
using Serilog;
|
||||
using Stylet;
|
||||
using Application = System.Windows.Forms.Application;
|
||||
using FontFamily = System.Windows.Media.FontFamily;
|
||||
|
||||
namespace MaaWpfGui.Helper
|
||||
{
|
||||
@@ -45,58 +35,75 @@ namespace MaaWpfGui.Helper
|
||||
/// </summary>
|
||||
public class ToastNotification : IDisposable
|
||||
{
|
||||
// 这玩意有用吗?
|
||||
// ReSharper disable once UnusedMember.Local
|
||||
private ToastNotification()
|
||||
private unsafe struct OSVERSIONINFOEXW
|
||||
{
|
||||
if (!CheckToastSystem())
|
||||
{
|
||||
NotificationConstants.MessagePosition = NotificationPosition.BottomRight;
|
||||
}
|
||||
public int dwOSVersionInfoSize;
|
||||
public int dwMajorVersion;
|
||||
public int dwMinorVersion;
|
||||
public int dwBuildNumber;
|
||||
public int dwPlatformId;
|
||||
private fixed char _szCSDVersion[128];
|
||||
public short wServicePackMajor;
|
||||
public short wServicePackMinor;
|
||||
public short wSuiteMask;
|
||||
public byte wProductType;
|
||||
public byte wReserved;
|
||||
|
||||
public Span<char> szCSDVersion => MemoryMarshal.CreateSpan(ref _szCSDVersion[0], 128);
|
||||
}
|
||||
|
||||
private static bool _systemToastChecked;
|
||||
private static bool _systemToastCheckInited;
|
||||
[DllImport("ntdll.dll", ExactSpelling = true)]
|
||||
private static extern int RtlGetVersion(ref OSVERSIONINFOEXW versionInfo);
|
||||
|
||||
// 这玩意有用吗?
|
||||
// ReSharper disable once UnusedMember.Local
|
||||
|
||||
private static INotificationPoster _notificationPoster;
|
||||
|
||||
private static readonly string _openUrlPrefix;
|
||||
|
||||
static ToastNotification()
|
||||
{
|
||||
_notificationPoster = GetNotificationPoster();
|
||||
_notificationPoster.ActionActivated += OnActionActivated;
|
||||
_openUrlPrefix = $"OpenUrl:{_notificationPoster.GetHashCode()}:";
|
||||
}
|
||||
|
||||
private static readonly ILogger _logger = Log.ForContext<ToastNotification>();
|
||||
|
||||
public static Action<string, string, NotifyIconInfoType> ShowBalloonTip { get; set; }
|
||||
|
||||
public static Action<string, Action> AddMenuItemOnFirst { get; set; }
|
||||
private static unsafe bool IsWindows10OrGreater()
|
||||
{
|
||||
var osVersionInfo = default(OSVERSIONINFOEXW);
|
||||
osVersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEXW);
|
||||
RtlGetVersion(ref osVersionInfo);
|
||||
var version = new Version(osVersionInfo.dwMajorVersion, osVersionInfo.dwMinorVersion, osVersionInfo.dwBuildNumber);
|
||||
return version > new Version(10, 0, 10240);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks toast system.
|
||||
/// </summary>
|
||||
/// <returns>The toast system is initialized.</returns>
|
||||
public bool CheckToastSystem()
|
||||
private static INotificationPoster GetNotificationPoster()
|
||||
{
|
||||
if (_systemToastCheckInited)
|
||||
if (WineRuntimeInformation.IsRunningUnderWine)
|
||||
{
|
||||
return _systemToastChecked;
|
||||
if (NotificationImplLibNotify.IsSupported)
|
||||
{
|
||||
return new NotificationImplLibNotify();
|
||||
}
|
||||
}
|
||||
|
||||
_systemToastCheckInited = true;
|
||||
|
||||
// like "Microsoft Windows 10.0.10240 "
|
||||
var osDesc = RuntimeInformation.OSDescription;
|
||||
Regex versionRegex = new Regex(@"\d+\.\d+\.\d+");
|
||||
var matched = versionRegex.Match(osDesc);
|
||||
if (!matched.Success)
|
||||
else if (IsWindows10OrGreater())
|
||||
{
|
||||
_systemToastChecked = false;
|
||||
return _systemToastChecked;
|
||||
return new NotificationImplWinRT();
|
||||
}
|
||||
|
||||
var osVersion = matched.Groups[0].Value;
|
||||
bool verParsed = SemVersion.TryParse(osVersion, SemVersionStyles.Strict, out var curVersionObj);
|
||||
|
||||
var minimumVersionObj = new SemVersion(10, 0, 10240);
|
||||
_systemToastChecked = verParsed && curVersionObj.CompareSortOrderTo(minimumVersionObj) >= 0;
|
||||
|
||||
return _systemToastChecked;
|
||||
return new NotificationImplWpf();
|
||||
}
|
||||
|
||||
private NotificationManager _notificationManager = new NotificationManager();
|
||||
/// <summary>
|
||||
/// 按钮激活后的事件,参数为按钮标签
|
||||
/// </summary>
|
||||
public static event EventHandler<string> ActionActivated;
|
||||
|
||||
/// <summary>
|
||||
/// 通知标题
|
||||
@@ -108,27 +115,6 @@ namespace MaaWpfGui.Helper
|
||||
/// </summary>
|
||||
private StringBuilder _contentCollection = new StringBuilder();
|
||||
|
||||
/// <summary>
|
||||
/// 应用图标资源
|
||||
/// </summary>
|
||||
private static ImageSource GetAppIcon()
|
||||
{
|
||||
try
|
||||
{
|
||||
var icon = Icon.ExtractAssociatedIcon(Application.ExecutablePath);
|
||||
var imageSource = Imaging.CreateBitmapSourceFromHIcon(
|
||||
icon!.Handle,
|
||||
Int32Rect.Empty,
|
||||
BitmapSizeOptions.FromEmptyOptions());
|
||||
|
||||
return imageSource;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return new BitmapImage();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ToastNotification"/> class.
|
||||
/// </summary>
|
||||
@@ -142,18 +128,16 @@ namespace MaaWpfGui.Helper
|
||||
{
|
||||
// 初始化通知标题
|
||||
_notificationTitle = title ?? _notificationTitle;
|
||||
}
|
||||
|
||||
// 同时显示最大数量
|
||||
NotificationConstants.NotificationsOverlayWindowMaxCount = 5;
|
||||
|
||||
// 默认显示位置
|
||||
// NotificationConstants.MessagePosition = NotificationPosition.BottomRight;
|
||||
|
||||
// 最小显示宽度
|
||||
NotificationConstants.MinWidth = 400d;
|
||||
|
||||
// 最大显示宽度
|
||||
NotificationConstants.MaxWidth = 460d;
|
||||
private static void OnActionActivated(object sender, string tag)
|
||||
{
|
||||
if (tag.StartsWith(_openUrlPrefix)) {
|
||||
var url = tag.Substring(_openUrlPrefix.Length);
|
||||
Process.Start(new ProcessStartInfo(url) { UseShellExecute = true });
|
||||
} else {
|
||||
ActionActivated?.Invoke(sender, tag);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -276,126 +260,36 @@ namespace MaaWpfGui.Helper
|
||||
|
||||
#region 通知按钮变量
|
||||
|
||||
// 左边按钮
|
||||
private string _buttonLeftText;
|
||||
|
||||
private Action _buttonLeftAction;
|
||||
|
||||
// 右边按钮
|
||||
private string _buttonRightText;
|
||||
|
||||
private Action _buttonRightAction;
|
||||
|
||||
// 系统按钮
|
||||
private string _buttonSystemText;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the button system URL.
|
||||
/// </summary>
|
||||
public string ButtonSystemUrl { get; set; } = string.Empty;
|
||||
|
||||
private bool _buttonSystemEnabled;
|
||||
private List<NotificationAction> _actions = new List<NotificationAction>();
|
||||
|
||||
#endregion 通知按钮变量
|
||||
|
||||
/// <summary>
|
||||
/// 给通知添加一个在左边的按钮,比如确定按钮,多次设置只会按最后一次设置生效
|
||||
/// 给通知添加一个按钮
|
||||
/// </summary>
|
||||
/// <param name="text">按钮标题</param>
|
||||
/// <param name="action">按钮执行方法</param>
|
||||
/// <param name="label">按钮标题</param>
|
||||
/// <param name="tag">事件标签</param>
|
||||
/// <returns>返回类本身,可继续调用其它方法</returns>
|
||||
public ToastNotification AddButtonLeft(string text, Action action)
|
||||
/// <remarks>按钮按下时,触发 <see cref="ActionActivated"/> 事件</remarks>
|
||||
public ToastNotification AddButton(string label, string tag)
|
||||
{
|
||||
AddMenuItemOnFirst(text, action); // TODO: 整理过时代码
|
||||
|
||||
_buttonLeftText = text;
|
||||
_buttonLeftAction = action;
|
||||
_buttonSystemText = text;
|
||||
_buttonSystemEnabled = true;
|
||||
_actions.Add(new NotificationAction(label, tag));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 给通知添加一个在右边的按钮,比如取消按钮,多次设置只会按最后一次设置生效
|
||||
/// </summary>
|
||||
/// <param name="text">按钮标题</param>
|
||||
/// <param name="action">按钮执行方法</param>
|
||||
/// <returns>返回类本身,可继续调用其它方法</returns>
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public ToastNotification AddButtonRight(string text, Action action)
|
||||
public static string GetActionTagForOpenWeb(string url)
|
||||
{
|
||||
AddMenuItemOnFirst(text, action); // TODO: 整理过时代码
|
||||
if (url.StartsWith("http://") || url.StartsWith("https://"))
|
||||
{
|
||||
return _openUrlPrefix + url;
|
||||
}
|
||||
|
||||
_buttonRightText = text;
|
||||
_buttonRightAction = action;
|
||||
_buttonSystemText = text;
|
||||
_buttonSystemEnabled = true;
|
||||
return this;
|
||||
throw new ArgumentException("URL must start with http:// or https://");
|
||||
}
|
||||
|
||||
#endregion 通知按钮设置
|
||||
|
||||
#region 通知显示
|
||||
|
||||
#region 通知基本字体样式和内容模板
|
||||
|
||||
/// <summary>
|
||||
/// Gets basic text styles.
|
||||
/// </summary>
|
||||
/// <remarks>创建一个基本文本字体样式。</remarks>
|
||||
public TextContentSettings BaseTextSettings => new TextContentSettings()
|
||||
{
|
||||
FontStyle = FontStyles.Normal,
|
||||
FontFamily = new FontFamily("Microsoft Yahei"),
|
||||
FontSize = 14,
|
||||
FontWeight = FontWeights.Normal,
|
||||
TextAlignment = TextAlignment.Left,
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch,
|
||||
VerticalTextAlignment = VerticalAlignment.Stretch,
|
||||
Opacity = 1d,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个基本通知内容模板
|
||||
/// </summary>
|
||||
/// <returns>The notification content.</returns>
|
||||
public NotificationContent BaseContent()
|
||||
{
|
||||
var content = new NotificationContent()
|
||||
{
|
||||
Title = _notificationTitle,
|
||||
Message = _contentCollection.ToString(),
|
||||
|
||||
Type = NotificationType.None,
|
||||
Icon = GetAppIcon(),
|
||||
CloseOnClick = true,
|
||||
|
||||
RowsCount = 1,
|
||||
TrimType = NotificationTextTrimType.AttachIfMoreRows,
|
||||
|
||||
Background = (SolidColorBrush)new BrushConverter().ConvertFrom("#FF1F3550"),
|
||||
|
||||
LeftButtonContent = _buttonLeftText ?? NotificationConstants.DefaultLeftButtonContent,
|
||||
LeftButtonAction = _buttonLeftAction,
|
||||
|
||||
RightButtonContent = _buttonRightText ?? NotificationConstants.DefaultRightButtonContent,
|
||||
RightButtonAction = _buttonRightAction,
|
||||
};
|
||||
|
||||
// 默认的标题文本样式
|
||||
var titleTextSettings = BaseTextSettings;
|
||||
titleTextSettings.FontSize = 18d;
|
||||
content.TitleTextSettings = titleTextSettings;
|
||||
|
||||
// 默认的内容文本样式
|
||||
var messageTextSettings = BaseTextSettings;
|
||||
content.MessageTextSettings = messageTextSettings;
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
#endregion 通知基本字体样式和内容模板
|
||||
|
||||
#region 显示通知方法
|
||||
|
||||
/// <summary>
|
||||
@@ -404,11 +298,10 @@ namespace MaaWpfGui.Helper
|
||||
/// <param name="lifeTime">通知显示时间 (s)</param>
|
||||
/// <param name="row">内容显示行数,如果内容太多建议使用 <see cref="ShowMore(double, uint, NotificationSounds, NotificationContent)"/></param>
|
||||
/// <param name="sound">播放提示音</param>
|
||||
/// <param name="notificationContent">通知内容</param>
|
||||
/// <param name="hints">通知额外元数据,可能影响通知展示方式</param>
|
||||
///
|
||||
public void Show(double lifeTime = 10d, uint row = 1,
|
||||
NotificationSounds sound = NotificationSounds.Notification,
|
||||
NotificationContent notificationContent = null)
|
||||
NotificationSounds sound = NotificationSounds.Notification, params NotificationHint[] hints)
|
||||
{
|
||||
// TODO: 整理过时代码
|
||||
if (!ConfigFactory.CurrentConfig.GUI.UseNotify)
|
||||
@@ -416,81 +309,35 @@ namespace MaaWpfGui.Helper
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
var content = new NotificationContent
|
||||
{
|
||||
if (CheckToastSystem())
|
||||
{
|
||||
Execute.OnUIThreadAsync(() =>
|
||||
{
|
||||
if (_buttonSystemEnabled)
|
||||
{
|
||||
Uri burl = new Uri(ButtonSystemUrl);
|
||||
new ToastContentBuilder()
|
||||
.AddText(_notificationTitle)
|
||||
.AddText(_contentCollection.ToString())
|
||||
.AddButton(new ToastButton()
|
||||
.SetContent(_buttonSystemText)
|
||||
.SetProtocolActivation(burl))
|
||||
.Show();
|
||||
/*
|
||||
var toastContent = new ToastContentBuilder()
|
||||
.AddText(_notificationTitle)
|
||||
.AddText(_contentCollection.ToString())
|
||||
.AddButton(new ToastButton()
|
||||
.SetContent(_buttonSystemText)
|
||||
.SetProtocolActivation(burl))
|
||||
.GetToastContent();
|
||||
var toastXmlDoc = new XmlDocument();
|
||||
toastXmlDoc.LoadXml(toastContent.GetContent());
|
||||
var toastNotification = new Windows.UI.Notifications.ToastNotification(toastXmlDoc);
|
||||
ToastNotificationManager.CreateToastNotifier().Show(toastNotification);
|
||||
*/
|
||||
}
|
||||
else
|
||||
{
|
||||
new ToastContentBuilder()
|
||||
.AddText(_notificationTitle)
|
||||
.AddText(_contentCollection.ToString())
|
||||
.Show();
|
||||
/*
|
||||
var toastContent = new ToastContentBuilder()
|
||||
.AddText(_notificationTitle)
|
||||
.AddText(_contentCollection.ToString())
|
||||
.GetToastContent();
|
||||
var toastXmlDoc = new XmlDocument();
|
||||
toastXmlDoc.LoadXml(toastContent.GetContent());
|
||||
var toastNotification = new Windows.UI.Notifications.ToastNotification(toastXmlDoc);
|
||||
ToastNotificationManager.CreateToastNotifier().Show(toastNotification);
|
||||
*/
|
||||
}
|
||||
});
|
||||
Summary = _notificationTitle,
|
||||
Body = _contentCollection.ToString(),
|
||||
};
|
||||
|
||||
// 通知正常弹出了就直接 return,否则用 catch 下面的兼容版通知
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
foreach (var action in _actions)
|
||||
{
|
||||
_logger.Error(e, "显示通知失败");
|
||||
_systemToastChecked = false;
|
||||
content.Actions.Add(action);
|
||||
}
|
||||
|
||||
notificationContent ??= BaseContent();
|
||||
|
||||
notificationContent.RowsCount = row;
|
||||
content.Hints.Add(NotificationHint.RowCount((int)row));
|
||||
|
||||
// 调整显示时间,如果存在按钮的情况下显示时间将强制设为最大时间
|
||||
lifeTime = lifeTime < 3d ? 3d : lifeTime;
|
||||
|
||||
var timeSpan = _buttonLeftAction == null && _buttonRightAction == null
|
||||
var timeSpan = _actions.Count != 0
|
||||
? TimeSpan.FromSeconds(lifeTime)
|
||||
: TimeSpan.MaxValue;
|
||||
|
||||
content.Hints.Add(NotificationHint.ExpirationTime(timeSpan));
|
||||
|
||||
foreach (var hint in hints)
|
||||
{
|
||||
content.Hints.Add(hint);
|
||||
}
|
||||
|
||||
// 显示通知
|
||||
_notificationManager.Show(
|
||||
notificationContent,
|
||||
expirationTime: timeSpan,
|
||||
ShowXbtn: false);
|
||||
_notificationPoster.ShowNotification(content);
|
||||
|
||||
// 播放通知提示音
|
||||
PlayNotificationSound(sound);
|
||||
@@ -505,18 +352,16 @@ namespace MaaWpfGui.Helper
|
||||
/// <param name="lifeTime">通知显示时间 (s)</param>
|
||||
/// <param name="row">内容显示行数,只用于预览一部分通知,多出内容会放在附加按钮的窗口中</param>
|
||||
/// <param name="sound">播放提示音,不设置就没有声音</param>
|
||||
/// <param name="notificationContent">通知内容</param>
|
||||
public void ShowMore(double lifeTime = 12d, uint row = 2,
|
||||
NotificationSounds sound = NotificationSounds.None,
|
||||
NotificationContent notificationContent = null)
|
||||
NotificationSounds sound = NotificationSounds.None, params NotificationHint[] hints)
|
||||
{
|
||||
notificationContent ??= BaseContent();
|
||||
notificationContent.TrimType = NotificationTextTrimType.Attach;
|
||||
|
||||
var morehints = new NotificationHint[hints.Length + 1];
|
||||
hints.CopyTo(morehints, 0);
|
||||
morehints[hints.Length] = NotificationHint.Expandable;
|
||||
Show(lifeTime: lifeTime,
|
||||
row: row,
|
||||
sound: sound,
|
||||
notificationContent: notificationContent);
|
||||
hints: morehints);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -525,15 +370,9 @@ namespace MaaWpfGui.Helper
|
||||
/// <param name="row">内容显示行数,比如第 2 行用来放星星</param>
|
||||
public void ShowRecruit(uint row = 1)
|
||||
{
|
||||
var content = BaseContent();
|
||||
|
||||
// 给通知染上资深标签相似的颜色
|
||||
content.Background = (SolidColorBrush)new BrushConverter().ConvertFrom("#FF401804");
|
||||
content.Foreground = (SolidColorBrush)new BrushConverter().ConvertFrom("#FFFFC800");
|
||||
|
||||
Show(row: row,
|
||||
sound: NotificationSounds.Notification,
|
||||
notificationContent: content);
|
||||
hints: NotificationHint.RecruitHighRarity);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -542,15 +381,9 @@ namespace MaaWpfGui.Helper
|
||||
/// <param name="row">内容显示行数,比如第 2 行用来放星星</param>
|
||||
public void ShowRecruitRobot(uint row = 1)
|
||||
{
|
||||
var content = BaseContent();
|
||||
|
||||
// 给通知染上小车相似的颜色
|
||||
content.Background = (SolidColorBrush)new BrushConverter().ConvertFrom("#FFFFFFF4");
|
||||
content.Foreground = (SolidColorBrush)new BrushConverter().ConvertFrom("#FF111111");
|
||||
|
||||
Show(row: row,
|
||||
sound: NotificationSounds.Notification,
|
||||
notificationContent: content);
|
||||
hints: NotificationHint.RecruitRobot);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -559,13 +392,9 @@ namespace MaaWpfGui.Helper
|
||||
/// <param name="row">内容行数</param>
|
||||
public void ShowUpdateVersion(uint row = 3)
|
||||
{
|
||||
var content = BaseContent();
|
||||
|
||||
content.Background = (SolidColorBrush)new BrushConverter().ConvertFrom("#FF007280");
|
||||
|
||||
ShowMore(row: row,
|
||||
sound: NotificationSounds.Notification,
|
||||
notificationContent: content);
|
||||
hints: NotificationHint.NewVersion);
|
||||
}
|
||||
|
||||
#endregion 显示通知方法
|
||||
@@ -693,12 +522,18 @@ namespace MaaWpfGui.Helper
|
||||
public void Dispose()
|
||||
{
|
||||
_contentCollection.Clear();
|
||||
}
|
||||
|
||||
_notificationManager = null;
|
||||
_notificationTitle = null;
|
||||
_contentCollection = null;
|
||||
_buttonLeftText = _buttonRightText = null;
|
||||
_buttonLeftAction = _buttonRightAction = null;
|
||||
public static void Cleanup()
|
||||
{
|
||||
try
|
||||
{
|
||||
(_notificationPoster as IDisposable)?.Dispose();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.ForContext<ToastNotification>().Error(e, "Cleanup error");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<UseWpf>true</UseWpf>
|
||||
<Configurations>Debug;Release;RelWithDebInfo</Configurations>
|
||||
<Platforms>ARM64;x64</Platforms>
|
||||
<EnableWindowsTargeting>true</EnableWindowsTargeting>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- Build and Publish -->
|
||||
|
||||
@@ -19,6 +19,8 @@ using System.Runtime.InteropServices;
|
||||
using System.Security.Principal;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
using GlobalHotKey;
|
||||
using MaaWpfGui.Helper;
|
||||
@@ -30,7 +32,7 @@ using MaaWpfGui.Services.Web;
|
||||
using MaaWpfGui.States;
|
||||
using MaaWpfGui.ViewModels.UI;
|
||||
using MaaWpfGui.Views.UI;
|
||||
using Microsoft.Toolkit.Uwp.Notifications;
|
||||
using MaaWpfGui.WineCompat;
|
||||
using Serilog;
|
||||
using Serilog.Core;
|
||||
using Stylet;
|
||||
@@ -120,6 +122,14 @@ namespace MaaWpfGui.Main
|
||||
_logger.Information("Run as Administrator");
|
||||
}
|
||||
|
||||
if (WineRuntimeInformation.IsRunningUnderWine)
|
||||
{
|
||||
_logger.Information($"Running under Wine {WineRuntimeInformation.WineVersion} on {WineRuntimeInformation.HostSystemName}");
|
||||
RenderOptions.ProcessRenderMode = RenderMode.SoftwareOnly;
|
||||
_logger.Information($"MaaWineBridge status: {MaaWineBridge.Availability}");
|
||||
_logger.Information($"MaaDesktopIntegration available: {MaaDesktopIntegration.Availabile}");
|
||||
}
|
||||
|
||||
_logger.Information("===================================");
|
||||
|
||||
try
|
||||
@@ -211,12 +221,7 @@ namespace MaaWpfGui.Main
|
||||
Instances.MaaHotKeyManager.Release();
|
||||
|
||||
// 关闭程序时清理操作中心中的通知
|
||||
var os = RuntimeInformation.OSDescription;
|
||||
if (string.Compare(os, "Microsoft Windows 10.0.10240", StringComparison.Ordinal) >= 0)
|
||||
{
|
||||
// new ToastNotificationHistory().Clear();
|
||||
ToastNotificationManagerCompat.History.Clear();
|
||||
}
|
||||
ToastNotification.Cleanup();
|
||||
|
||||
ConfigurationHelper.Release();
|
||||
|
||||
@@ -229,7 +234,7 @@ namespace MaaWpfGui.Main
|
||||
{
|
||||
ProcessStartInfo startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = System.Windows.Forms.Application.ExecutablePath,
|
||||
FileName = Environment.ProcessPath,
|
||||
};
|
||||
|
||||
Process.Start(startInfo);
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
<Style x:Key="MdXamlStyle" TargetType="FlowDocument">
|
||||
<Setter Property="Background" Value="{DynamicResource MdXamlBackground}" />
|
||||
<Setter Property="FontFamily" Value="Microsoft YaHei" />
|
||||
<Setter Property="FontFamily" Value="Microsoft YaHei, Global User Interface" />
|
||||
<Setter Property="FontSize" Value="16" />
|
||||
<Setter Property="TextAlignment" Value="Left" />
|
||||
<Setter Property="PagePadding" Value="10" />
|
||||
@@ -57,7 +57,7 @@
|
||||
<Setter Property="FontWeight" Value="Bold" />
|
||||
</Trigger>
|
||||
<Trigger Property="Tag" Value="CodeBlock">
|
||||
<Setter Property="FontFamily" Value="Consolas, Microsoft YaHei" />
|
||||
<Setter Property="FontFamily" Value="Consolas, Microsoft YaHei, Global Monospace" />
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="Background" Value="{DynamicResource MdXamlCodeBackground}" />
|
||||
<Setter Property="Padding" Value="20, 10" />
|
||||
@@ -76,7 +76,7 @@
|
||||
<Style.Triggers>
|
||||
<Trigger Property="Tag" Value="CodeSpan">
|
||||
<Setter Property="Background" Value="{DynamicResource MdXamlCodeBackground}" />
|
||||
<Setter Property="FontFamily" Value="Consolas, Microsoft YaHei" />
|
||||
<Setter Property="FontFamily" Value="Consolas, Microsoft YaHei, Global Monospace" />
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
@@ -86,7 +86,7 @@
|
||||
<Style.Triggers>
|
||||
<Trigger Property="Tag" Value="CodeSpan">
|
||||
<Setter Property="Background" Value="{DynamicResource MdXamlCodeBackground}" />
|
||||
<Setter Property="FontFamily" Value="Consolas, Microsoft YaHei" />
|
||||
<Setter Property="FontFamily" Value="Consolas, Microsoft YaHei, Global Monospace" />
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace MaaWpfGui.Utilities
|
||||
{
|
||||
private static readonly ILogger _logger = Log.ForContext("SourceContext", "AutoStart");
|
||||
|
||||
private static readonly string _fileValue = Process.GetCurrentProcess().MainModule?.FileName;
|
||||
private static readonly string _fileValue = Environment.ProcessPath;
|
||||
private static readonly string _uniqueIdentifier = GetHashCode(_fileValue);
|
||||
|
||||
private static readonly string _startupFolderPath = Environment.GetFolderPath(Environment.SpecialFolder.Startup);
|
||||
|
||||
@@ -43,6 +43,7 @@ using MaaWpfGui.Services.RemoteControl;
|
||||
using MaaWpfGui.States;
|
||||
using MaaWpfGui.Utilities;
|
||||
using MaaWpfGui.Utilities.ValueType;
|
||||
using MaaWpfGui.WineCompat;
|
||||
using Microsoft.Win32;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
@@ -3608,10 +3609,11 @@ namespace MaaWpfGui.ViewModels.UI
|
||||
// ReSharper disable once UnusedMember.Global
|
||||
public void SelectFile()
|
||||
{
|
||||
var dialog = new OpenFileDialog
|
||||
var dialog = new OpenFileDialog();
|
||||
if (MaaWineBridge.Availability == WineBridgeAvailability.NotAvailable)
|
||||
{
|
||||
Filter = LocalizationHelper.GetString("AdbProgram") + "|*.exe",
|
||||
};
|
||||
dialog.Filter = LocalizationHelper.GetString("AdbProgram") + "|*.exe";
|
||||
}
|
||||
|
||||
if (dialog.ShowDialog() == true)
|
||||
{
|
||||
|
||||
@@ -503,8 +503,7 @@ namespace MaaWpfGui.ViewModels.UI
|
||||
}
|
||||
}
|
||||
|
||||
toast.AddButtonLeft(text, action);
|
||||
toast.ButtonSystemUrl = UpdateUrl;
|
||||
toast.AddButton(text, ToastNotification.GetActionTagForOpenWeb(UpdateUrl));
|
||||
toast.ShowUpdateVersion();
|
||||
});
|
||||
|
||||
@@ -593,10 +592,9 @@ namespace MaaWpfGui.ViewModels.UI
|
||||
OutputDownloadProgress(downloading: false, output: LocalizationHelper.GetString("NewVersionDownloadFailedTitle"));
|
||||
_ = Execute.OnUIThreadAsync(() =>
|
||||
{
|
||||
using var toast = new ToastNotification(LocalizationHelper.GetString("NewVersionDownloadFailedTitle"));
|
||||
toast.ButtonSystemUrl = UpdateUrl;
|
||||
var toast = new ToastNotification(LocalizationHelper.GetString("NewVersionDownloadFailedTitle"));
|
||||
toast.AppendContentText(LocalizationHelper.GetString("NewVersionDownloadFailedDesc"))
|
||||
.AddButtonLeft(text, action)
|
||||
.AddButton(text, ToastNotification.GetActionTagForOpenWeb(UpdateUrl))
|
||||
.Show();
|
||||
});
|
||||
return CheckUpdateRetT.NoNeedToUpdate;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
xmlns:mdxam="clr-namespace:MdXaml;assembly=MdXaml"
|
||||
xmlns:styles="clr-namespace:MaaWpfGui.Styles"
|
||||
xmlns:ui="clr-namespace:MaaWpfGui.ViewModels.UI"
|
||||
Icon="../../newlogo.ico"
|
||||
Title="{DynamicResource Announcement}"
|
||||
Width="600"
|
||||
Height="500"
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:styles="clr-namespace:MaaWpfGui.Styles"
|
||||
x:Name="ErrorViewWindow"
|
||||
Icon="../../newlogo.ico"
|
||||
Title="{DynamicResource Error}"
|
||||
Width="600"
|
||||
Height="480"
|
||||
|
||||
@@ -33,12 +33,12 @@ namespace MaaWpfGui.Views.UI
|
||||
InitializeComponent();
|
||||
InitIcon();
|
||||
_menuItemNum = notifyIcon.ContextMenu.Items.Count;
|
||||
ToastNotification.ShowBalloonTip = notifyIcon.ShowBalloonTip;
|
||||
ToastNotification.AddMenuItemOnFirst = AddMenuItemOnFirst;
|
||||
}
|
||||
|
||||
private void InitIcon()
|
||||
{
|
||||
notifyIcon.Icon = AppIcon.GetIcon();
|
||||
|
||||
notifyIcon.Click += NotifyIcon_MouseClick;
|
||||
notifyIcon.MouseDoubleClick += OnNotifyIconDoubleClick;
|
||||
|
||||
|
||||
@@ -154,7 +154,7 @@
|
||||
Margin="10"
|
||||
HorizontalAlignment="Stretch"
|
||||
BorderThickness="0"
|
||||
FontFamily="SimSun"
|
||||
FontFamily="SimSun, Global Monospace"
|
||||
FontSize="14"
|
||||
IsReadOnly="True"
|
||||
Text="{Binding OperBoxResult}"
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
xmlns:ui="clr-namespace:MaaWpfGui.ViewModels.UI"
|
||||
xmlns:viewModels="clr-namespace:MaaWpfGui.ViewModels"
|
||||
xmlns:vm="clr-namespace:MaaWpfGui"
|
||||
Icon="../../newlogo.ico"
|
||||
Title="{DynamicResource VersionUpdated}"
|
||||
Width="600"
|
||||
Height="500"
|
||||
|
||||
261
src/MaaWpfGui/WineCompat/FontConfig/FontConfigIntegration.cs
Normal file
261
src/MaaWpfGui/WineCompat/FontConfig/FontConfigIntegration.cs
Normal file
@@ -0,0 +1,261 @@
|
||||
// <copyright file="FontConfigIntegration.cs" company="MaaAssistantArknights">
|
||||
// MaaWpfGui - A part of the MAA project
|
||||
// Copyright (C) 2021 MistEO and Contributors
|
||||
// </copyright>
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Markup;
|
||||
using System.Windows.Media;
|
||||
using MaaWpfGui.Helper;
|
||||
using Serilog;
|
||||
using static MaaWpfGui.WineCompat.FontConfig.Native;
|
||||
|
||||
namespace MaaWpfGui.WineCompat.FontConfig;
|
||||
|
||||
public unsafe class FontConfigIntegration
|
||||
{
|
||||
private readonly ref struct Defer(Action action)
|
||||
{
|
||||
public void Dispose() => action();
|
||||
}
|
||||
|
||||
public static void Install(string wpf, string fontconfig)
|
||||
{
|
||||
var wpffont = new FontFamily(wpf);
|
||||
var fceq = GetFontFamilyFromPattern(fontconfig);
|
||||
if (fceq == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var map = wpffont.FamilyMaps;
|
||||
map.Clear();
|
||||
int i = 0;
|
||||
foreach (var item in fceq.FamilyMaps)
|
||||
{
|
||||
map.Insert(i++, item);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Install()
|
||||
{
|
||||
Debug.WriteLine($"FontConfigIntegration Install");
|
||||
// GC.KeepAlive(DefaultFont.UserInterface);
|
||||
// DefaultFont.UserInterface = GetFontFamilyFromPattern("system-ui");
|
||||
// DefaultFont.Monospace = GetFontFamilyFromPattern("monospace");
|
||||
// DefaultFont.SansSerif = GetFontFamilyFromPattern("sans-serif");
|
||||
// DefaultFont.Serif = GetFontFamilyFromPattern("serif");
|
||||
|
||||
Install("Global User Interface", "system-ui");
|
||||
Install("Global Monospace", "monospace");
|
||||
Install("Global Sans Serif", "sans-serif");
|
||||
Install("Global Serif", "serif");
|
||||
}
|
||||
|
||||
public static FontFamily GetFontFamilyFromPattern(string pattern)
|
||||
{
|
||||
var logger = Log.ForContext<FontConfigIntegration>();
|
||||
logger.Information($"GetFontFamilyFromPattern: {pattern}");
|
||||
var config = FcInitLoadConfigAndFonts();
|
||||
using var _dtor_config = new Defer(() => FcConfigDestroy(config));
|
||||
|
||||
var fcPattern = FcNameParse(pattern);
|
||||
using var _dtor_pattern = new Defer(() => FcPatternDestroy(fcPattern));
|
||||
|
||||
FcConfigSubstitute(config, fcPattern, FcMatchKind.Pattern);
|
||||
FcDefaultSubstitute(fcPattern);
|
||||
|
||||
var fs = FcFontSort(config, fcPattern, FcBool.True, null, out var result);
|
||||
if (fs == null)
|
||||
{
|
||||
logger.Error("FcFontSort failed");
|
||||
return null;
|
||||
}
|
||||
|
||||
var wpffont = new FontFamily();
|
||||
wpffont.FamilyNames.Add(XmlLanguage.GetLanguage("en-us"), "FontConfig " + pattern);
|
||||
|
||||
var resolved = FcCharSetCreate();
|
||||
using var _dtor_resolved = new Defer(() => FcCharSetDestroy(resolved));
|
||||
|
||||
for (var i = 0; i < fs->nfont; i++)
|
||||
{
|
||||
var item = new FontFamilyMap();
|
||||
result = FcPatternGetString(fs->fonts[i], "family", 0, out var pfamily);
|
||||
if (result == FcResult.Match)
|
||||
{
|
||||
item.Target = Marshal.PtrToStringUTF8(pfamily);
|
||||
logger.Information(item.Target);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error("FcPatternGetString failed: {0}", result);
|
||||
continue;
|
||||
}
|
||||
|
||||
result = FcPatternGetCharSet(fs->fonts[i], "charset", 0, out var s);
|
||||
if (result == FcResult.Match)
|
||||
{
|
||||
var fallback = FcCharSetSubtract(s, resolved);
|
||||
using var _dtor_fallback = new Defer(() => FcCharSetDestroy(fallback));
|
||||
FcCharSetMerge(resolved, s, out _);
|
||||
var range = CharSetToUnicodeRange(fallback);
|
||||
item.Unicode = range;
|
||||
logger.Information($"{item.Target} {range}");
|
||||
wpffont.FamilyMaps.Add(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Error("FcPatternGetCharSet failed: {0}", result);
|
||||
}
|
||||
}
|
||||
|
||||
return wpffont;
|
||||
}
|
||||
|
||||
private static string CharSetToUnicodeRange(FcCharSet* cs)
|
||||
{
|
||||
// return "0020-10FFFF";
|
||||
Span<uint> bitmap = stackalloc uint[FC_CHARSET_MAP_SIZE];
|
||||
var sb = new StringBuilder();
|
||||
|
||||
var offset = FcCharSetFirstPage(cs, ref bitmap[0], out var next);
|
||||
|
||||
var range_start = 0u;
|
||||
var range_end = 0u;
|
||||
bool in_range = false;
|
||||
|
||||
if ((bitmap[0] & 1) != 0)
|
||||
{
|
||||
BeginRange(offset);
|
||||
}
|
||||
|
||||
void BeginRange(uint start)
|
||||
{
|
||||
range_start = start;
|
||||
range_end = start;
|
||||
in_range = true;
|
||||
}
|
||||
|
||||
void AdvanceRange(uint length)
|
||||
{
|
||||
if (!in_range)
|
||||
{
|
||||
throw new InvalidOperationException("range not started");
|
||||
}
|
||||
|
||||
range_end += length;
|
||||
}
|
||||
|
||||
void EndRange()
|
||||
{
|
||||
if (!in_range)
|
||||
{
|
||||
throw new InvalidOperationException("range not started");
|
||||
}
|
||||
|
||||
if (sb.Length > 0)
|
||||
{
|
||||
sb.Append(",");
|
||||
}
|
||||
|
||||
if (range_end - range_start == 1)
|
||||
{
|
||||
sb.Append($"{range_start:X4}");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append($"{range_start:X4}-{range_end - 1:X4}");
|
||||
}
|
||||
|
||||
in_range = false;
|
||||
range_start = 0;
|
||||
range_end = 0;
|
||||
}
|
||||
|
||||
while (offset != FC_CHARSET_DONE)
|
||||
{
|
||||
for (var i = 0; i < bitmap.Length; i++)
|
||||
{
|
||||
var field = bitmap[i];
|
||||
var bit_offset = 0u;
|
||||
|
||||
// happy path: no bits unset
|
||||
if (field == 0xFFFFFFFF)
|
||||
{
|
||||
if (!in_range)
|
||||
{
|
||||
BeginRange(offset + ((uint)i * 32));
|
||||
}
|
||||
|
||||
AdvanceRange(32);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (field == 0)
|
||||
{
|
||||
if (in_range)
|
||||
{
|
||||
EndRange();
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
while (field != 0 && bit_offset < 32)
|
||||
{
|
||||
var trailing_0 = BitOperations.TrailingZeroCount(field);
|
||||
if (trailing_0 > 0)
|
||||
{
|
||||
if (in_range)
|
||||
{
|
||||
EndRange();
|
||||
}
|
||||
|
||||
field >>= trailing_0;
|
||||
bit_offset += (uint)trailing_0;
|
||||
if (field != 0)
|
||||
{
|
||||
BeginRange(offset + ((uint)i * 32) + bit_offset);
|
||||
}
|
||||
}
|
||||
|
||||
var trailing_1 = BitOperations.TrailingZeroCount(~field);
|
||||
if (trailing_1 > 0)
|
||||
{
|
||||
if (!in_range)
|
||||
{
|
||||
BeginRange(offset + ((uint)i * 32) + bit_offset);
|
||||
}
|
||||
|
||||
AdvanceRange((uint)trailing_1);
|
||||
field >>= trailing_1;
|
||||
bit_offset += (uint)trailing_1;
|
||||
if (field == 0 && bit_offset < 32)
|
||||
{
|
||||
EndRange();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (next == FC_CHARSET_DONE)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (in_range && next != offset + (bitmap.Length * 32))
|
||||
{
|
||||
EndRange();
|
||||
}
|
||||
|
||||
offset = FcCharSetNextPage(cs, ref bitmap[0], ref next);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
67
src/MaaWpfGui/WineCompat/FontConfig/Native.cs
Normal file
67
src/MaaWpfGui/WineCompat/FontConfig/Native.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
// <copyright file="Native.cs" company="MaaAssistantArknights">
|
||||
// MaaWpfGui - A part of the MAA project
|
||||
// Copyright (C) 2021 MistEO and Contributors
|
||||
// </copyright>
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MaaWpfGui.WineCompat.FontConfig;
|
||||
|
||||
internal unsafe class Native
|
||||
{
|
||||
private const string DllName = MaaDesktopIntegration.DllName;
|
||||
|
||||
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode, ExactSpelling = true)]
|
||||
public static extern FcConfig* FcInitLoadConfigAndFonts();
|
||||
|
||||
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode, ExactSpelling = true)]
|
||||
public static extern FcPattern* FcNameParse([MarshalAs(UnmanagedType.LPUTF8Str)] string pattern);
|
||||
|
||||
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode, ExactSpelling = true)]
|
||||
public static extern FcBool FcConfigSubstitute(FcConfig* config, FcPattern* pattern, FcMatchKind kind);
|
||||
|
||||
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode, ExactSpelling = true)]
|
||||
public static extern void FcDefaultSubstitute(FcPattern* pattern);
|
||||
|
||||
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode, ExactSpelling = true)]
|
||||
public static extern FcResult FcPatternGetString(FcPattern* pattern, [MarshalAs(UnmanagedType.LPUTF8Str)] string obj, int n, out nint s);
|
||||
|
||||
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode, ExactSpelling = true)]
|
||||
public static extern FcResult FcPatternGetCharSet(FcPattern* pattern, [MarshalAs(UnmanagedType.LPUTF8Str)] string obj, int n, out FcCharSet* s);
|
||||
|
||||
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode, ExactSpelling = true)]
|
||||
public static extern FcFontSet* FcFontSort(FcConfig* config, FcPattern* pattern, FcBool trim, nint* blanks, out FcResult result);
|
||||
|
||||
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode, ExactSpelling = true)]
|
||||
public static extern FcCharSet* FcCharSetCreate();
|
||||
|
||||
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode, ExactSpelling = true)]
|
||||
public static extern FcCharSet* FcCharSetSubtract(FcCharSet* a, FcCharSet* b);
|
||||
|
||||
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode, ExactSpelling = true)]
|
||||
public static extern int FcCharSetMerge(FcCharSet* a, FcCharSet* b, out FcBool changed);
|
||||
|
||||
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode, ExactSpelling = true)]
|
||||
public static extern uint FcCharSetFirstPage(FcCharSet* a, ref uint map, out uint next);
|
||||
|
||||
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode, ExactSpelling = true)]
|
||||
public static extern uint FcCharSetNextPage(FcCharSet* a, ref uint map, ref uint next);
|
||||
|
||||
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode, ExactSpelling = true)]
|
||||
public static extern void FcCharSetDestroy(FcCharSet* a);
|
||||
|
||||
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode, ExactSpelling = true)]
|
||||
public static extern void FcPatternDestroy(FcPattern* pattern);
|
||||
|
||||
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode, ExactSpelling = true)]
|
||||
public static extern void FcFontSetDestroy(FcFontSet* fs);
|
||||
|
||||
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode, ExactSpelling = true)]
|
||||
public static extern void FcConfigDestroy(FcConfig* config);
|
||||
|
||||
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode, ExactSpelling = true)]
|
||||
public static extern int MaaFcCharsetMapSize();
|
||||
|
||||
public static int FC_CHARSET_MAP_SIZE { get; } = MaaFcCharsetMapSize();
|
||||
|
||||
public const uint FC_CHARSET_DONE = unchecked((uint)-1);
|
||||
}
|
||||
44
src/MaaWpfGui/WineCompat/FontConfig/Types.cs
Normal file
44
src/MaaWpfGui/WineCompat/FontConfig/Types.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
// <copyright file="Types.cs" company="MaaAssistantArknights">
|
||||
// MaaWpfGui - A part of the MAA project
|
||||
// Copyright (C) 2021 MistEO and Contributors
|
||||
// </copyright>
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MaaWpfGui.WineCompat.FontConfig;
|
||||
|
||||
internal enum FcResult
|
||||
{
|
||||
Match,
|
||||
NoMatch,
|
||||
TypeMismatch,
|
||||
NoId,
|
||||
OutOfMemory
|
||||
}
|
||||
|
||||
internal enum FcMatchKind
|
||||
{
|
||||
Pattern,
|
||||
Font,
|
||||
Scan,
|
||||
FcMatchKindEnd,
|
||||
FcMatchKindBegin = Pattern
|
||||
}
|
||||
|
||||
internal unsafe struct FcFontSet
|
||||
{
|
||||
public int nfont;
|
||||
public int sfont;
|
||||
public FcPattern** fonts;
|
||||
}
|
||||
|
||||
internal struct FcConfig { }
|
||||
internal struct FcPattern { }
|
||||
internal struct FcCharSet { }
|
||||
|
||||
internal enum FcBool
|
||||
{
|
||||
False,
|
||||
True,
|
||||
DontCare
|
||||
}
|
||||
34
src/MaaWpfGui/WineCompat/GObject.cs
Normal file
34
src/MaaWpfGui/WineCompat/GObject.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
// <copyright file="GObject.cs" company="MaaAssistantArknights">
|
||||
// MaaWpfGui - A part of the MaaCoreArknights project
|
||||
// Copyright (C) 2021 MistEO and Contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY
|
||||
// </copyright>
|
||||
|
||||
using System;
|
||||
|
||||
using static MaaWpfGui.WineCompat.MaaDesktopIntegration;
|
||||
|
||||
namespace MaaWpfGui.WineCompat
|
||||
{
|
||||
internal class GObject
|
||||
{
|
||||
public nint Handle { get; }
|
||||
|
||||
protected GObject(nint handle)
|
||||
{
|
||||
Handle = handle;
|
||||
}
|
||||
|
||||
~GObject()
|
||||
{
|
||||
g_object_unref(Handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
60
src/MaaWpfGui/WineCompat/GdkPixBuf.cs
Normal file
60
src/MaaWpfGui/WineCompat/GdkPixBuf.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
// <copyright file="GdkPixBuf.cs" company="MaaAssistantArknights">
|
||||
// MaaWpfGui - A part of the MaaCoreArknights project
|
||||
// Copyright (C) 2021 MistEO and Contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY
|
||||
// </copyright>
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using static MaaWpfGui.WineCompat.MaaDesktopIntegration;
|
||||
|
||||
namespace MaaWpfGui.WineCompat;
|
||||
|
||||
internal class GdkPixBuf : GObject
|
||||
{
|
||||
public const int GDK_COLORSPACE_RGB = 0;
|
||||
|
||||
private class PinnedPixels : IDisposable
|
||||
{
|
||||
public MemoryHandle Handle { get; }
|
||||
|
||||
public PinnedPixels(Memory<byte> pixels)
|
||||
{
|
||||
Handle = pixels.Pin();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Handle.Dispose();
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvStdcall) })]
|
||||
public static void DestroyCallback(IntPtr pixels, IntPtr data)
|
||||
{
|
||||
var handle = GCHandle.FromIntPtr(data);
|
||||
handle.Free();
|
||||
}
|
||||
}
|
||||
|
||||
private GdkPixBuf(nint handle)
|
||||
: base(handle)
|
||||
{
|
||||
}
|
||||
|
||||
public static unsafe GdkPixBuf CreateFromPixels(int width, int height, int rowStride, bool has_alpha, Memory<byte> pixels)
|
||||
{
|
||||
var pin = new PinnedPixels(pixels);
|
||||
var gch = GCHandle.Alloc(pin);
|
||||
var obj = gdk_pixbuf_new_from_data((IntPtr)pin.Handle.Pointer, GDK_COLORSPACE_RGB, has_alpha ? 1 : 0, 8, width, height, rowStride, &PinnedPixels.DestroyCallback, GCHandle.ToIntPtr(gch));
|
||||
return new GdkPixBuf(obj);
|
||||
}
|
||||
}
|
||||
137
src/MaaWpfGui/WineCompat/LibNotifyNotification.cs
Normal file
137
src/MaaWpfGui/WineCompat/LibNotifyNotification.cs
Normal file
@@ -0,0 +1,137 @@
|
||||
// <copyright file="LibNotifyNotification.cs" company="MaaAssistantArknights">
|
||||
// MaaWpfGui - A part of the MaaCoreArknights project
|
||||
// Copyright (C) 2021 MistEO and Contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY
|
||||
// </copyright>
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using static MaaWpfGui.WineCompat.MaaDesktopIntegration;
|
||||
|
||||
namespace MaaWpfGui.WineCompat;
|
||||
|
||||
internal class LibNotifyNotification : GObject
|
||||
{
|
||||
protected GCHandle WeakGCHandle { get; }
|
||||
|
||||
public LibNotifyNotification(string title, string message, string iconPath)
|
||||
: base(CreateObject(title, message, iconPath))
|
||||
{
|
||||
WeakGCHandle = GCHandle.Alloc(this, GCHandleType.Weak);
|
||||
unsafe
|
||||
{
|
||||
g_signal_connect_data(Handle, "closed", &CloseSignalCallback, GCHandle.ToIntPtr(WeakGCHandle), &CloseSignalFreeCallback, 0);
|
||||
}
|
||||
}
|
||||
|
||||
~LibNotifyNotification()
|
||||
{
|
||||
WeakGCHandle.Free();
|
||||
}
|
||||
|
||||
private static IntPtr CreateObject(string title, string message, string iconPath)
|
||||
{
|
||||
return notify_notification_new(title, message, iconPath);
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvStdcall) })]
|
||||
private static void ActionCallback(nint notification, nint action, nint userData)
|
||||
{
|
||||
var instance = GCHandle.FromIntPtr(userData).Target as LibNotifyNotification;
|
||||
instance?.ActionActivated?.Invoke(instance, Marshal.PtrToStringUTF8(action)!);
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvStdcall) })]
|
||||
private static void ActionFreeCallback(nint userData)
|
||||
{
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvStdcall) })]
|
||||
private static void CloseSignalCallback(nint notification, nint userData)
|
||||
{
|
||||
var instance = GCHandle.FromIntPtr(userData).Target as LibNotifyNotification;
|
||||
instance?.Closed?.Invoke(instance, EventArgs.Empty);
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvStdcall) })]
|
||||
private static void CloseSignalFreeCallback(nint userData, nint closure)
|
||||
{
|
||||
}
|
||||
|
||||
public event EventHandler<string> ActionActivated;
|
||||
|
||||
public event EventHandler Closed;
|
||||
|
||||
public unsafe void AddAction(string tag, string label)
|
||||
{
|
||||
notify_notification_add_action(Handle, tag, label, &ActionCallback, GCHandle.ToIntPtr(WeakGCHandle), &ActionFreeCallback);
|
||||
}
|
||||
|
||||
public void ClearActions()
|
||||
{
|
||||
notify_notification_clear_actions(Handle);
|
||||
}
|
||||
|
||||
public void ClearHints()
|
||||
{
|
||||
notify_notification_clear_hints(Handle);
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
notify_notification_close(Handle, out var error);
|
||||
}
|
||||
|
||||
public void SetAppIcon(string appIcon)
|
||||
{
|
||||
notify_notification_set_app_icon(Handle, appIcon);
|
||||
}
|
||||
|
||||
public void SetAppName(string appName)
|
||||
{
|
||||
notify_notification_set_app_name(Handle, appName);
|
||||
}
|
||||
|
||||
public void SetCategory(string category)
|
||||
{
|
||||
notify_notification_set_category(Handle, category);
|
||||
}
|
||||
|
||||
public void SetHint(string key, nint value)
|
||||
{
|
||||
notify_notification_set_hint(Handle, key, value);
|
||||
}
|
||||
|
||||
public void SetImageFromPixbuf(GdkPixBuf pixbuf)
|
||||
{
|
||||
notify_notification_set_image_from_pixbuf(Handle, pixbuf.Handle);
|
||||
}
|
||||
|
||||
public void SetTimeout(int timeout)
|
||||
{
|
||||
notify_notification_set_timeout(Handle, timeout);
|
||||
}
|
||||
|
||||
public void SetUrgency(int urgency)
|
||||
{
|
||||
notify_notification_set_urgency(Handle, urgency);
|
||||
}
|
||||
|
||||
public void Show()
|
||||
{
|
||||
notify_notification_show(Handle, out var error);
|
||||
}
|
||||
|
||||
public bool Update(string title, string message, string iconPath)
|
||||
{
|
||||
return notify_notification_update(Handle, title, message, iconPath) != 0;
|
||||
}
|
||||
}
|
||||
107
src/MaaWpfGui/WineCompat/MaaDesktopIntegration.cs
Normal file
107
src/MaaWpfGui/WineCompat/MaaDesktopIntegration.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
// <copyright file="MaaWineBridge.cs" company="MaaAssistantArknights">
|
||||
// MaaWpfGui - A part of the MaaCoreArknights project
|
||||
// Copyright (C) 2021 MistEO and Contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY
|
||||
// </copyright>
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MaaWpfGui.WineCompat;
|
||||
|
||||
internal unsafe class MaaDesktopIntegration
|
||||
{
|
||||
public static bool Availabile { get; }
|
||||
|
||||
static MaaDesktopIntegration()
|
||||
{
|
||||
if (WineRuntimeInformation.IsRunningUnderWine)
|
||||
{
|
||||
try
|
||||
{
|
||||
glib_default_main_loop_start();
|
||||
AppDomain.CurrentDomain.ProcessExit += (sender, args) => glib_default_main_loop_stop();
|
||||
Availabile = true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public const string DllName = "MaaDesktopIntegration.so";
|
||||
|
||||
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int glib_default_main_loop_start();
|
||||
|
||||
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void glib_default_main_loop_stop();
|
||||
|
||||
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int notify_init([MarshalAs(UnmanagedType.LPUTF8Str)] string app_name);
|
||||
|
||||
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void g_object_unref(IntPtr ptr);
|
||||
|
||||
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern IntPtr notify_notification_new([MarshalAs(UnmanagedType.LPUTF8Str)] string summary, [MarshalAs(UnmanagedType.LPUTF8Str)] string body, [MarshalAs(UnmanagedType.LPUTF8Str)] string icon);
|
||||
|
||||
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void notify_notification_add_action(IntPtr notification, [MarshalAs(UnmanagedType.LPUTF8Str)] string action, [MarshalAs(UnmanagedType.LPUTF8Str)] string label, delegate* unmanaged[Stdcall]<nint, nint, nint, void> callback, IntPtr user_data, delegate* unmanaged[Stdcall]<nint, void> free_func);
|
||||
|
||||
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void notify_notification_clear_actions(IntPtr notification);
|
||||
|
||||
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void notify_notification_clear_hints(IntPtr notification);
|
||||
|
||||
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void notify_notification_close(IntPtr notification, out IntPtr error);
|
||||
|
||||
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern IntPtr notify_notification_get_activation_token(IntPtr notification);
|
||||
|
||||
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int notify_notification_get_closed_reason(IntPtr notification);
|
||||
|
||||
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void notify_notification_set_app_icon(IntPtr notification, [MarshalAs(UnmanagedType.LPUTF8Str)] string app_icon);
|
||||
|
||||
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void notify_notification_set_app_name(IntPtr notification, [MarshalAs(UnmanagedType.LPUTF8Str)] string app_name);
|
||||
|
||||
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void notify_notification_set_category(IntPtr notification, [MarshalAs(UnmanagedType.LPUTF8Str)] string category);
|
||||
|
||||
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void notify_notification_set_hint(IntPtr notification, [MarshalAs(UnmanagedType.LPUTF8Str)] string key, IntPtr value);
|
||||
|
||||
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void notify_notification_set_image_from_pixbuf(IntPtr notification, IntPtr pixbuf);
|
||||
|
||||
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void notify_notification_set_timeout(IntPtr notification, int timeout);
|
||||
|
||||
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void notify_notification_set_urgency(IntPtr notification, int urgency);
|
||||
|
||||
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void notify_notification_show(IntPtr notification, out IntPtr error);
|
||||
|
||||
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern int notify_notification_update(IntPtr notification, [MarshalAs(UnmanagedType.LPUTF8Str)] string summary, [MarshalAs(UnmanagedType.LPUTF8Str)] string body, [MarshalAs(UnmanagedType.LPUTF8Str)] string icon);
|
||||
|
||||
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern IntPtr gdk_pixbuf_new_from_data(IntPtr data, int colorspace, int has_alpha, int bits_per_sample, int width, int height, int rowstride, delegate* unmanaged[Stdcall]<nint, nint, void> destroy_fn, IntPtr destroy_fn_data);
|
||||
|
||||
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern IntPtr g_signal_connect_data(nint instance, [MarshalAs(UnmanagedType.LPUTF8Str)] string detailed_signal, delegate* unmanaged[Stdcall]<nint, nint, void> c_handler, nint data, delegate* unmanaged[Stdcall]<nint, nint, void> destroy_data, uint connect_flags);
|
||||
}
|
||||
42
src/MaaWpfGui/WineCompat/MaaWineBridge.cs
Normal file
42
src/MaaWpfGui/WineCompat/MaaWineBridge.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
// <copyright file="MaaWineBridge.cs" company="MaaAssistantArknights">
|
||||
// MaaWpfGui - A part of the MAA project
|
||||
// Copyright (C) 2021 MistEO and Contributors
|
||||
// </copyright>
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace MaaWpfGui.WineCompat;
|
||||
|
||||
internal static class MaaWineBridge
|
||||
{
|
||||
// MaaWineBridge
|
||||
[DllImport("MaaCore.dll", CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern int dl_has_maacore();
|
||||
|
||||
public static WineBridgeAvailability Availability { get; }
|
||||
|
||||
static MaaWineBridge()
|
||||
{
|
||||
if (!WineRuntimeInformation.IsRunningUnderWine)
|
||||
{
|
||||
Availability = WineBridgeAvailability.NotAvailable;
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (dl_has_maacore() != 0)
|
||||
{
|
||||
Availability = WineBridgeAvailability.Operational;
|
||||
}
|
||||
else
|
||||
{
|
||||
Availability = WineBridgeAvailability.Faulted;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Availability = WineBridgeAvailability.NotAvailable;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
src/MaaWpfGui/WineCompat/WineBridgeAvailability.cs
Normal file
11
src/MaaWpfGui/WineCompat/WineBridgeAvailability.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
// <copyright file="WineBridgeAvailability.cs" company="MaaAssistantArknights">
|
||||
// MaaWpfGui - A part of the MAA project
|
||||
// Copyright (C) 2021 MistEO and Contributors
|
||||
// </copyright>
|
||||
|
||||
public enum WineBridgeAvailability
|
||||
{
|
||||
Operational,
|
||||
Faulted,
|
||||
NotAvailable,
|
||||
}
|
||||
59
src/MaaWpfGui/WineCompat/WineRuntimeInformation.cs
Normal file
59
src/MaaWpfGui/WineCompat/WineRuntimeInformation.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
// <copyright file="WineRuntimeInformation.cs" company="MaaAssistantArknights">
|
||||
// MaaWpfGui - A part of the MaaCoreArknights project
|
||||
// Copyright (C) 2021 MistEO and Contributors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY
|
||||
// </copyright>
|
||||
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MaaWpfGui.WineCompat
|
||||
{
|
||||
internal class WineRuntimeInformation
|
||||
{
|
||||
[DllImport("ntdll.dll", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern IntPtr wine_get_version();
|
||||
|
||||
[DllImport("ntdll.dll", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern void wine_get_host_version(out IntPtr sysname, out IntPtr release);
|
||||
|
||||
public static bool IsRunningUnderWine { get; }
|
||||
|
||||
public static string? WineVersion { get; }
|
||||
|
||||
public static string? HostSystemName { get; }
|
||||
|
||||
static WineRuntimeInformation()
|
||||
{
|
||||
try
|
||||
{
|
||||
var ptr = wine_get_version();
|
||||
if (ptr != IntPtr.Zero)
|
||||
{
|
||||
IsRunningUnderWine = true;
|
||||
}
|
||||
|
||||
WineVersion = Marshal.PtrToStringUTF8(ptr);
|
||||
wine_get_host_version(out var sysname, out var release);
|
||||
HostSystemName = Marshal.PtrToStringUTF8(sysname);
|
||||
}
|
||||
catch
|
||||
{
|
||||
IsRunningUnderWine = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user