Compare commits

...

6 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
45Tatami a64823de29 Replace and fix configuration storage 2022-03-17 23:35:49 +01:00
45Tatami 3f06f09553 Do not degrade performance over time 2022-01-18 22:23:58 +01:00
45Tatami e603a58a5d
Add screenshot 2022-01-17 18:19:30 +01:00
8 changed files with 228 additions and 94 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,7 +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. Configuration done at runtime via interface.
![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. Drop resulting dll into Textractor extension window.
Configuration done at runtime via interface. ### 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,20 +1,22 @@
#include "extension.h"
#include "resource.h" #include "resource.h"
#include "Extension.h"
#include <atomic> #include <atomic>
#include <codecvt> #include <codecvt>
#include <condition_variable> #include <condition_variable>
#include <deque> #include <deque>
#include <filesystem>
#include <fstream>
#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;
@ -23,36 +25,51 @@ 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"
#define CONFIG_ENTRY_REMOTE L"Remote" #define CONFIG_ENTRY_REMOTE L"Remote"
#define CONFIG_ENTRY_CONNECT L"WantConnect" #define CONFIG_ENTRY_CONNECT L"WantConnect"
#define CONFIG_FILE_NAME L"Textractor.ini" #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"";
@ -60,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);
@ -70,17 +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 cur = getEditBoxText(hwnd, IDC_LOG); msg.copy(buf, msg.length());
if (cur.length() > 0) PostMessage(win_hndl, WM_USR_LOG, (WPARAM) NULL, (LPARAM) buf);
cur += L"\r\n";
wstring tmp =
cur + 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)
@ -90,38 +101,30 @@ void log(wstring const& msg)
log(tmp); log(tmp);
} }
void write_config_val(LPCSTR key, LPCSTR val)
{
}
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;
if (hwnd == NULL)
return;
HWND edit = GetDlgItem(hwnd, IDC_REMOTE);
if (want_connect) {
SetDlgItemText(hwnd, IDC_BTN_SUBMIT, L"Disconnect");
SendMessage(edit, EM_SETREADONLY, TRUE, NULL);
} else {
SetDlgItemText(hwnd, IDC_BTN_SUBMIT, L"Connect");
SendMessage(edit, EM_SETREADONLY, FALSE, NULL);
} }
conn_cv.notify_one(); void save_config(path const& filepath, wstring const& remote, bool connect)
{
std::wofstream f{filepath, std::ios_base::trunc};
if (f.good()) {
f << remote.c_str() << "\n";
f << connect;
}
else {
MessageBox(NULL, L"Could not open config file for writing", L"Error", 0);
}
} }
/** /**
* 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;
@ -129,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;
@ -139,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) {
@ -195,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)
@ -222,6 +231,73 @@ INT_PTR CALLBACK DialogProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lPara
} }
return true; return true;
} }
case WM_USR_LOG:
{
wstring tmp = getEditBoxText(hWnd, IDC_LOG);
if (tmp.length() > 0)
tmp.append(L"\r\n");
// Remove older text to not slow down
if (tmp.length() > 4000) {
tmp.erase(0, 2000);
}
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()) {
log("Config file does not exist.");
goto config_done;
}
std::getline(f, remote);
SetDlgItemText(win_hndl, IDC_REMOTE, remote.c_str());
bool connect;
f >> connect;
if (connect)
toggle_want_connect();
config_done:
comm_thread_run = true;
config_initialized = true;
conn_cv.notify_one();
return true;
}
default: default:
return false; return false;
} }
@ -233,6 +309,21 @@ BOOL WINAPI DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved
{ {
case DLL_PROCESS_ATTACH: case DLL_PROCESS_ATTACH:
{ {
// 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
win_hndl = CreateDialogParam(hmod, MAKEINTRESOURCE(IDD_DIALOG1),
NULL, DialogProc, 0);
if (win_hndl == NULL) {
MessageBox(NULL, L"Could not open plugin dialog", L"Error", 0);
return false;
}
ShowWindow(win_hndl, SW_NORMAL);
wchar_t* buf; wchar_t* buf;
// Get config path // Get config path
@ -243,62 +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 = wstring{buf} + CONFIG_FILE_NAME; config_file_path = path{wstring{buf}} / CONFIG_FILE_NAME;
GlobalFree(buf); GlobalFree(buf);
// Get configured remote PostMessage(win_hndl, WM_USR_LOAD_CONFIG,
buf = (wchar_t*)GlobalAlloc(GPTR, 1000 * sizeof(wchar_t)); (WPARAM) NULL, (LPARAM) config_file_path.c_str());
if (buf == NULL)
return false;
GetPrivateProfileString(CONFIG_APP_NAME, CONFIG_ENTRY_REMOTE,
remote.c_str(), buf, 1000, config_file_path.c_str());
remote = wstring{buf};
GlobalFree(buf);
// Get configured connection state
UINT w = GetPrivateProfileInt(
CONFIG_APP_NAME, CONFIG_ENTRY_CONNECT,
want_connect, config_file_path.c_str());
// Create window
hwnd = CreateDialogParam(hModule, MAKEINTRESOURCE(IDD_DIALOG1),
FindWindow(NULL, L"Textractor"), DialogProc, 0);
if (hwnd == NULL) {
MessageBox(NULL, L"Could not open plugin dialog", L"Error", 0);
return false;
}
if (w)
toggle_want_connect();
// 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);
WritePrivateProfileString(
CONFIG_APP_NAME, CONFIG_ENTRY_CONNECT,
(want_connect ? L"1" : L"0"), config_file_path.c_str());
WritePrivateProfileString(
CONFIG_APP_NAME, CONFIG_ENTRY_REMOTE,
remote.c_str(), config_file_path.c_str());
} }
break; break;
} }
@ -379,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

@ -119,6 +119,8 @@
<ConformanceMode>true</ConformanceMode> <ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>NotUsing</PrecompiledHeader> <PrecompiledHeader>NotUsing</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile> <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<AdditionalOptions>/w34996 %(AdditionalOptions)</AdditionalOptions>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile> </ClCompile>
<Link> <Link>
<SubSystem>Windows</SubSystem> <SubSystem>Windows</SubSystem>
@ -153,6 +155,8 @@
<ConformanceMode>true</ConformanceMode> <ConformanceMode>true</ConformanceMode>
<PrecompiledHeader>NotUsing</PrecompiledHeader> <PrecompiledHeader>NotUsing</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile> <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<LanguageStandard>stdcpp17</LanguageStandard>
<AdditionalOptions>/w34996 %(AdditionalOptions)</AdditionalOptions>
</ClCompile> </ClCompile>
<Link> <Link>
<SubSystem>Windows</SubSystem> <SubSystem>Windows</SubSystem>

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)