Compare commits

...

3 Commits

Author SHA1 Message Date
45Tatami ad5bd38f9d implement (cross-)compilation via meson 2023-05-23 00:12:18 +02:00
45Tatami 43fb4e028d compile fixes for cross-compilation 2023-05-23 00:09:40 +02:00
45Tatami e258d15908 Stability improvements
Make more use of ui message queues.
Fix dll detach behavior (at the cost of cleanup) also fixes shutdown under wine
2022-06-12 14:36:22 +02:00
7 changed files with 215 additions and 93 deletions

2
.gitignore vendored
View File

@ -361,3 +361,5 @@ MigrationBackup/
# Fody - auto-generated XML schema # Fody - auto-generated XML schema
FodyWeavers.xsd FodyWeavers.xsd
*.swp

View File

@ -3,9 +3,29 @@
Useful for setups where the clipboard is not enough. Useful for setups where the clipboard is not enough.
See [here](https://github.com/45Tatami/native-inserter) for an example of the receiving server side. See [here](https://github.com/45Tatami/native-inserter) for an example of the receiving server side.
Requires Visual Studio 2019. Drop into VS, build x86 or x64 depending on which Textractor architecture you use.
Drop resulting dll into Textractor extension window.
Configuration done at runtime via interface. Configuration done at runtime via interface.
![Purrint_1707](https://user-images.githubusercontent.com/96940591/149813301-b10d229c-f093-43fa-a483-5848f71e9d2c.png) ![Purrint_1707](https://user-images.githubusercontent.com/96940591/149813301-b10d229c-f093-43fa-a483-5848f71e9d2c.png)
## Building
Requires Visual Studio 2019 or meson. Drop into VS, build x86 or x64 depending on which Textractor architecture you use.
Drop resulting dll into Textractor extension window.
### meson
```
meson setup build
ninja -Cbuild
```
Project includes example cross-compilation definition files for a mingw32 toolchain under `cross`.
```
# x86/32bit
meson setup --cross-file=cross/i686-w64-mingw32.txt build
# x86_64/64bit
meson setup --cross-file=cross/x86_64-w64-mingw32.txt build
```

View File

@ -1,4 +1,4 @@
#include "extension.h" #include "Extension.h"
bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo); bool ProcessSentence(std::wstring& sentence, SentenceInfo sentenceInfo);
@ -18,7 +18,7 @@ extern "C" __declspec(dllexport) wchar_t* OnNewSentence(wchar_t* sentence, const
try try
{ {
std::wstring sentenceCopy(sentence); std::wstring sentenceCopy(sentence);
int oldSize = sentenceCopy.size(); auto oldSize = sentenceCopy.size();
if (ProcessSentence(sentenceCopy, SentenceInfo{ sentenceInfo })) if (ProcessSentence(sentenceCopy, SentenceInfo{ sentenceInfo }))
{ {
if (sentenceCopy.size() > oldSize) sentence = (wchar_t*)HeapReAlloc(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS, sentence, (sentenceCopy.size() + 1) * sizeof(wchar_t)); if (sentenceCopy.size() > oldSize) sentence = (wchar_t*)HeapReAlloc(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS, sentence, (sentenceCopy.size() + 1) * sizeof(wchar_t));

View File

@ -1,5 +1,5 @@
#include "extension.h"
#include "resource.h" #include "resource.h"
#include "Extension.h"
#include <atomic> #include <atomic>
#include <codecvt> #include <codecvt>
@ -10,13 +10,13 @@
#include <locale> #include <locale>
#include <mutex> #include <mutex>
#include <string> #include <string>
#include <thread>
#include <windows.h> #include <windows.h>
#include <winsock2.h> #include <winsock2.h>
#include <ws2tcpip.h> #include <ws2tcpip.h>
#include <strsafe.h> #include <strsafe.h>
using std::filesystem::path;
using std::lock_guard; using std::lock_guard;
using std::mutex; using std::mutex;
using std::unique_lock; using std::unique_lock;
@ -25,9 +25,11 @@ using std::wstring;
using std::wstring_convert; using std::wstring_convert;
using std::codecvt_utf8_utf16; using std::codecvt_utf8_utf16;
#ifdef _MSC_VER
#pragma comment (lib, "Ws2_32.lib") #pragma comment (lib, "Ws2_32.lib")
#pragma comment (lib, "Mswsock.lib") #pragma comment (lib, "Mswsock.lib")
#pragma comment (lib, "AdvApi32.lib") #pragma comment (lib, "AdvApi32.lib")
#endif
#define MSG_Q_CAP 10 #define MSG_Q_CAP 10
#define CONFIG_APP_NAME L"TCPSend" #define CONFIG_APP_NAME L"TCPSend"
@ -35,26 +37,39 @@ using std::codecvt_utf8_utf16;
#define CONFIG_ENTRY_CONNECT L"WantConnect" #define CONFIG_ENTRY_CONNECT L"WantConnect"
#define CONFIG_FILE_NAME L"tcpsender.config" #define CONFIG_FILE_NAME L"tcpsender.config"
std::thread comm_thread; HMODULE hmod = NULL;
HWND win_hndl = NULL;
UINT const WM_USR_LOG = WM_APP + 1;
UINT const WM_USR_TOGGLE_CONNECT = WM_APP + 2;
UINT const WM_USR_LOAD_CONFIG = WM_APP + 3;
HANDLE comm_thread;
wstring remote = L"localhost:30501"; wstring remote = L"localhost:30501";
wstring config_file_path; wstring config_file_path;
HWND hwnd = NULL;
// Mutex/cv protects following vars // Mutex/cv protects following vars
mutex conn_mut; mutex conn_mut;
std::condition_variable conn_cv; std::condition_variable conn_cv;
std::atomic<bool> comm_thread_run; std::atomic<bool> comm_thread_run;
std::atomic<bool> want_connect; std::atomic<bool> want_connect;
std::atomic<bool> config_initialized;
std::deque<wstring> msg_q; std::deque<wstring> msg_q;
SOCKET _connect(); SOCKET _connect();
bool _send(SOCKET &, string const &); bool _send(SOCKET &, string const &);
wstring getEditBoxText(HWND hndl, int item) { wstring getEditBoxText(HWND win_hndl, int item) {
if (hwnd == NULL) if (win_hndl == NULL)
return L""; return L"";
int len = GetWindowTextLength(GetDlgItem(hndl, item)); HWND edit = GetDlgItem(win_hndl, item);
if (edit == NULL) {
MessageBox(NULL, L"Could not get editbox handle", L"Error", 0);
return L"";
}
int len = GetWindowTextLength(edit);
if (len == 0) if (len == 0)
return L""; return L"";
@ -62,7 +77,7 @@ wstring getEditBoxText(HWND hndl, int item) {
if (buf == NULL) if (buf == NULL)
return L""; return L"";
GetDlgItemText(hndl, item, buf, len + 1); GetDlgItemText(win_hndl, item, buf, len + 1);
wstring tmp = wstring{ buf }; wstring tmp = wstring{ buf };
GlobalFree(buf); GlobalFree(buf);
@ -72,22 +87,11 @@ wstring getEditBoxText(HWND hndl, int item) {
void log(string const& msg) void log(string const& msg)
{ {
if (hwnd == NULL) // Async to allow logging from dialog thread
return; // Freed on message handling
char* buf = (char *) GlobalAlloc(GPTR, msg.length() + 1);
wstring tmp = getEditBoxText(hwnd, IDC_LOG); msg.copy(buf, msg.length());
if (tmp.length() > 0) PostMessage(win_hndl, WM_USR_LOG, (WPARAM) NULL, (LPARAM) buf);
tmp.append(L"\r\n");
// Remove older text to not slow down
if (tmp.length() > 4000) {
tmp.erase(0, 2000);
}
tmp.append(wstring_convert<codecvt_utf8_utf16<wchar_t>>().from_bytes(msg));
SetDlgItemText(hwnd, IDC_LOG, tmp.c_str());
SendMessage(GetDlgItem(hwnd, IDC_LOG), EM_LINESCROLL, 0, INT_MAX);
} }
void log(wstring const& msg) void log(wstring const& msg)
@ -99,31 +103,28 @@ void log(wstring const& msg)
void toggle_want_connect() void toggle_want_connect()
{ {
unique_lock<mutex> conn_lk{conn_mut}; PostMessage(win_hndl, WM_USR_TOGGLE_CONNECT, (WPARAM) NULL, (LPARAM) NULL);
}
want_connect = !want_connect; void save_config(path const& filepath, wstring const& remote, bool connect)
{
if (hwnd == NULL) std::wofstream f{filepath, std::ios_base::trunc};
return; if (f.good()) {
f << remote.c_str() << "\n";
HWND edit = GetDlgItem(hwnd, IDC_REMOTE); f << connect;
}
if (want_connect) { else {
SetDlgItemText(hwnd, IDC_BTN_SUBMIT, L"Disconnect"); MessageBox(NULL, L"Could not open config file for writing", L"Error", 0);
SendMessage(edit, EM_SETREADONLY, TRUE, NULL);
} else {
SetDlgItemText(hwnd, IDC_BTN_SUBMIT, L"Connect");
SendMessage(edit, EM_SETREADONLY, FALSE, NULL);
} }
conn_cv.notify_one();
} }
/** /**
* Connect to remote and wait for messages in queue to send until comm_thread_run is false * Connect to remote and wait for messages in queue to send until comm_thread_run is false
*/ */
void comm_loop() DWORD WINAPI comm_loop(LPVOID lpParam)
{ {
(void)lpParam;
using namespace std::chrono_literals; using namespace std::chrono_literals;
WSADATA wsaData; WSADATA wsaData;
@ -131,7 +132,7 @@ void comm_loop()
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
log("Could not initialize WSA. Exit"); log("Could not initialize WSA. Exit");
return; return 1;
} }
SOCKET sock = INVALID_SOCKET; SOCKET sock = INVALID_SOCKET;
@ -141,8 +142,12 @@ void comm_loop()
// again to see what happened and on long operations like connection // again to see what happened and on long operations like connection
// attempts or sending data. // attempts or sending data.
// This allows protecting muliple variables without performance problems. // This allows protecting muliple variables without performance problems.
comm_thread_run = true;
unique_lock<mutex> lk{conn_mut}; unique_lock<mutex> lk{conn_mut};
while (!config_initialized) {
conn_cv.wait(lk);
}
while (comm_thread_run) { while (comm_thread_run) {
// If we are not connected, try to connect if wanted, wait if we don't // If we are not connected, try to connect if wanted, wait if we don't
if (sock == INVALID_SOCKET) { if (sock == INVALID_SOCKET) {
@ -197,6 +202,8 @@ void comm_loop()
closesocket(sock); closesocket(sock);
WSACleanup(); WSACleanup();
return 0;
} }
INT_PTR CALLBACK DialogProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) INT_PTR CALLBACK DialogProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
@ -224,35 +231,75 @@ INT_PTR CALLBACK DialogProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lPara
} }
return true; return true;
} }
default: case WM_USR_LOG:
return false; {
} wstring tmp = getEditBoxText(hWnd, IDC_LOG);
} if (tmp.length() > 0)
tmp.append(L"\r\n");
void load_config(wstring const &filepath) // Remove older text to not slow down
{ if (tmp.length() > 4000) {
log(wstring{L"Loading config: "} + filepath); tmp.erase(0, 2000);
std::wifstream f{filepath}; }
char* buf = (char *) lParam;
tmp.append(wstring_convert<codecvt_utf8_utf16<wchar_t>>().from_bytes(buf));
GlobalFree(buf);
SetDlgItemText(hWnd, IDC_LOG, tmp.c_str());
SendMessage(GetDlgItem(hWnd, IDC_LOG), EM_LINESCROLL, 0, INT_MAX);
return true;
}
case WM_USR_TOGGLE_CONNECT:
{
lock_guard<mutex> conn_lk{ conn_mut };
want_connect = !want_connect;
HWND edit = GetDlgItem(hWnd, IDC_REMOTE);
if (want_connect) {
SetDlgItemText(hWnd, IDC_BTN_SUBMIT, L"Disconnect");
SendMessage(edit, EM_SETREADONLY, TRUE, (LPARAM) NULL);
}
else {
SetDlgItemText(hWnd, IDC_BTN_SUBMIT, L"Connect");
SendMessage(edit, EM_SETREADONLY, FALSE, (LPARAM) NULL);
}
save_config(config_file_path, remote, want_connect);
conn_cv.notify_one();
return true;
}
case WM_USR_LOAD_CONFIG:
{
lock_guard<mutex> conn_lk{ conn_mut };
log(L"Loading config: " + wstring{(wchar_t*) lParam});
std::wifstream f{(wchar_t *) lParam};
if (f.fail()) { if (f.fail()) {
log("Config file does not exist."); log("Config file does not exist.");
return; goto config_done;
} }
std::getline(f, remote); std::getline(f, remote);
SetDlgItemText(hwnd, IDC_REMOTE, remote.c_str()); SetDlgItemText(win_hndl, IDC_REMOTE, remote.c_str());
bool connect; bool connect;
f >> connect; f >> connect;
if (connect) if (connect)
toggle_want_connect(); toggle_want_connect();
}
void save_config(wstring const& filepath, wstring const& remote, bool connect) config_done:
{ comm_thread_run = true;
std::wofstream f{filepath, std::ios_base::trunc}; config_initialized = true;
if (f.good()) { conn_cv.notify_one();
f << remote.c_str() << "\n";
f << connect; return true;
}
default:
return false;
} }
} }
@ -262,16 +309,22 @@ BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved
{ {
case DLL_PROCESS_ATTACH: case DLL_PROCESS_ATTACH:
{ {
wchar_t* buf; // We need to create the Window here since otherwise it will be owned
// by some worker thread
// But try to do as few things as possible
hmod = hModule;
// Create window // Create window
hwnd = CreateDialogParam(hModule, MAKEINTRESOURCE(IDD_DIALOG1), win_hndl = CreateDialogParam(hmod, MAKEINTRESOURCE(IDD_DIALOG1),
FindWindow(NULL, L"Textractor"), DialogProc, 0); NULL, DialogProc, 0);
if (hwnd == NULL) { if (win_hndl == NULL) {
MessageBox(NULL, L"Could not open plugin dialog", L"Error", 0); MessageBox(NULL, L"Could not open plugin dialog", L"Error", 0);
return false; return false;
} }
ShowWindow(win_hndl, SW_NORMAL);
wchar_t* buf;
// Get config path // Get config path
DWORD buf_sz = (GetCurrentDirectory(0, NULL) + 1) * sizeof(wchar_t); DWORD buf_sz = (GetCurrentDirectory(0, NULL) + 1) * sizeof(wchar_t);
@ -281,30 +334,30 @@ BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved
GetCurrentDirectory(buf_sz, buf); GetCurrentDirectory(buf_sz, buf);
config_file_path = std::filesystem::path{wstring{buf} + L"/" + CONFIG_FILE_NAME}; config_file_path = path{wstring{buf}} / CONFIG_FILE_NAME;
GlobalFree(buf); GlobalFree(buf);
load_config(config_file_path); PostMessage(win_hndl, WM_USR_LOAD_CONFIG,
(WPARAM) NULL, (LPARAM) config_file_path.c_str());
// Start communication thread // Start communication thread
comm_thread = std::thread{comm_loop}; comm_thread = CreateThread(NULL, 0, comm_loop, NULL, 0, NULL);
} }
break; break;
case DLL_PROCESS_DETACH: case DLL_PROCESS_DETACH:
{ {
unique_lock<mutex> lk{conn_mut}; // Signal and wait for cleanup of comm thread would be good but
comm_thread_run = false; // join/WaitForSingleObject does not work in DLL_PROCESS_DETACH
lk.unlock();
conn_cv.notify_one(); // unique_lock<mutex> lk{ conn_mut };
// comm_thread_run = false;
// conn_cv.notify_one();
// lk.unlock();
if (comm_thread.joinable()) // if (comm_thread.joinable())
comm_thread.join(); // comm_thread.join();
if (hwnd != NULL) DestroyWindow(win_hndl);
CloseWindow(hwnd);
save_config(config_file_path, remote, want_connect);
} }
break; break;
} }
@ -385,6 +438,7 @@ bool ProcessSentence(wstring & sentence, SentenceInfo sentenceInfo)
{ {
if (sentenceInfo["current select"]) { if (sentenceInfo["current select"]) {
log("Received sentence"); log("Received sentence");
lock_guard<mutex> lock{conn_mut}; lock_guard<mutex> lock{conn_mut};
if (msg_q.size() >= MSG_Q_CAP) if (msg_q.size() >= MSG_Q_CAP)

View File

@ -0,0 +1,13 @@
[binaries]
c = 'i686-w64-mingw32-gcc'
cpp = 'i686-w64-mingw32-g++'
ar = 'i686-w64-mingw32-ar'
strip = 'i686-w64-mingw32-strip'
windres = 'i686-w64-mingw32-windres'
exe_wrapper = 'wine'
[host_machine]
system = 'windows'
cpu_family = 'x86'
cpu = 'i686'
endian = 'little'

View File

@ -0,0 +1,13 @@
[binaries]
c = 'x86_64-w64-mingw32-gcc'
cpp = 'x86_64-w64-mingw32-g++'
ar = 'x86_64-w64-mingw32-ar'
strip = 'x86_64-w64-mingw32-strip'
windres = 'x86_64-w64-mingw32-windres'
exe_wrapper = 'wine64'
[host_machine]
system = 'windows'
cpu_family = 'x86_64'
cpu = 'x86_64'
endian = 'little'

20
meson.build Normal file
View File

@ -0,0 +1,20 @@
project('Textractor-TCPSender', 'cpp',
default_options : ['cpp_std=c++17'])
add_project_arguments('-DUNICODE', language : 'cpp')
compiler = meson.get_compiler('cpp')
src = files(
'TCPSender/TCPSender.cpp',
'TCPSender/ExtensionImpl.cpp'
)
windows = import('windows')
src += windows.compile_resources('TCPSender/resource.rc')
deps = []
deps += compiler.find_library('ws2_32')
library('tcpsender', src,
dependencies : deps)