aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/ui/syntax/syntaxedit.c
diff options
context:
space:
mode:
Diffstat (limited to 'ui/syntax/syntaxedit.c')
-rw-r--r--ui/syntax/syntaxedit.c311
1 files changed, 311 insertions, 0 deletions
diff --git a/ui/syntax/syntaxedit.c b/ui/syntax/syntaxedit.c
new file mode 100644
index 00000000..5586e9d1
--- /dev/null
+++ b/ui/syntax/syntaxedit.c
@@ -0,0 +1,311 @@
+/* SPDX-License-Identifier: MIT
+ *
+ * Copyright (C) 2019 WireGuard LLC. All Rights Reserved.
+ */
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+#include <assert.h>
+#include <windows.h>
+#include <richedit.h>
+#include <richole.h>
+#include <tom.h>
+
+#include "syntaxedit.h"
+#include "highlighter.h"
+
+const GUID CDECL IID_ITextDocument = { 0x8CC497C0, 0xA1DF, 0x11CE, { 0x80, 0x98, 0x00, 0xAA, 0x00, 0x47, 0xBE, 0x5D } };
+
+struct syntaxedit_data {
+ IRichEditOle *irich;
+ ITextDocument *idoc;
+};
+
+static WNDPROC parent_proc;
+
+struct span_style {
+ COLORREF color;
+ DWORD effects;
+};
+
+static const struct span_style stylemap[] = {
+ [HighlightSection] = { .color = RGB(0x32, 0x6D, 0x74), .effects = CFE_BOLD },
+ [HighlightField] = { .color = RGB(0x9B, 0x23, 0x93), .effects = CFE_BOLD },
+ [HighlightPrivateKey] = { .color = RGB(0x64, 0x38, 0x20) },
+ [HighlightPublicKey] = { .color = RGB(0x64, 0x38, 0x20) },
+ [HighlightPresharedKey] = { .color = RGB(0x64, 0x38, 0x20) },
+ [HighlightIP] = { .color = RGB(0x0E, 0x0E, 0xFF) },
+ [HighlightCidr] = { .color = RGB(0x81, 0x5F, 0x03) },
+ [HighlightHost] = { .color = RGB(0x0E, 0x0E, 0xFF) },
+ [HighlightPort] = { .color = RGB(0x81, 0x5F, 0x03) },
+ [HighlightMTU] = { .color = RGB(0x1C, 0x00, 0xCF) },
+ [HighlightKeepalive] = { .color = RGB(0x1C, 0x00, 0xCF) },
+ [HighlightComment] = { .color = RGB(0x53, 0x65, 0x79), .effects = CFE_ITALIC },
+ [HighlightDelimiter] = { .color = RGB(0x00, 0x00, 0x00) },
+#ifndef MOBILE_WGQUICK_SUBSET
+ [HighlightTable] = { .color = RGB(0x1C, 0x00, 0xCF) },
+ [HighlightFwMark] = { .color = RGB(0x1C, 0x00, 0xCF) },
+ [HighlightSaveConfig] = { .color = RGB(0x81, 0x5F, 0x03) },
+ [HighlightCmd] = { .color = RGB(0x63, 0x75, 0x89) },
+#endif
+ [HighlightError] = { .color = RGB(0xC4, 0x1A, 0x16), .effects = CFE_UNDERLINE }
+};
+
+static void highlight_text(HWND hWnd)
+{
+ GETTEXTLENGTHEX gettextlengthex = {
+ .flags = GTL_NUMBYTES,
+ .codepage = CP_ACP /* Probably CP_UTF8 would be better, but (wine at least) returns utf32 sizes. */
+ };
+ GETTEXTEX gettextex = {
+ .flags = GT_NOHIDDENTEXT,
+ .codepage = gettextlengthex.codepage
+ };
+ CHARFORMAT2 format = {
+ .cbSize = sizeof(CHARFORMAT2),
+ .dwMask = CFM_COLOR | CFM_CHARSET | CFM_SIZE | CFM_BOLD | CFM_ITALIC | CFM_UNDERLINE,
+ .dwEffects = CFE_AUTOCOLOR,
+ .yHeight = 20 * 10,
+ .bCharSet = ANSI_CHARSET
+ };
+ struct syntaxedit_data *this = (struct syntaxedit_data *)GetWindowLongPtr(hWnd, GWLP_USERDATA);
+ LRESULT msg_size;
+ char *msg;
+ struct highlight_span *spans;
+ CHARRANGE orig_selection;
+ POINT original_scroll;
+ bool found_private_key = false;
+
+ msg_size = SendMessage(hWnd, EM_GETTEXTLENGTHEX, (WPARAM)&gettextlengthex, 0);
+ if (msg_size == E_INVALIDARG)
+ return;
+ gettextex.cb = msg_size + 1;
+
+ msg = malloc(msg_size + 1);
+ if (!msg)
+ return;
+ if (SendMessage(hWnd, EM_GETTEXTEX, (WPARAM)&gettextex, (LPARAM)msg) <= 0) {
+ free(msg);
+ return;
+ }
+
+ /* By default we get CR not CRLF, so just convert to LF. */
+ for (size_t i = 0; i < msg_size; ++i) {
+ if (msg[i] == '\r')
+ msg[i] = '\n';
+ }
+
+ spans = highlight_config(msg);
+ if (!spans) {
+ free(msg);
+ return;
+ }
+
+ this->idoc->lpVtbl->Undo(this->idoc, tomSuspend, NULL);
+ SendMessage(hWnd, WM_SETREDRAW, FALSE, 0);
+ SendMessage(hWnd, EM_EXGETSEL, 0, (LPARAM)&orig_selection);
+ SendMessage(hWnd, EM_GETSCROLLPOS, 0, (LPARAM)&original_scroll);
+ SendMessage(hWnd, EM_HIDESELECTION, TRUE, 0);
+ SendMessage(hWnd, EM_SETCHARFORMAT, SCF_ALL, (LPARAM)&format);
+ for (struct highlight_span *span = spans; span->type != HighlightEnd; ++span) {
+ CHARRANGE selection = { span->start, span->len + span->start };
+ SendMessage(hWnd, EM_EXSETSEL, 0, (LPARAM)&selection);
+ format.crTextColor = stylemap[span->type].color;
+ format.dwEffects = stylemap[span->type].effects;
+ SendMessage(hWnd, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&format);
+ if (span->type == HighlightPrivateKey && !found_private_key) {
+ /* Rather than allocating a new string, we mangle this one, since (for now) we don't use msg again. */
+ msg[span->start + span->len] = '\0';
+ SendMessage(hWnd, SE_PRIVATE_KEY, 0, (LPARAM)&msg[span->start]);
+ found_private_key = true;
+ }
+ }
+ SendMessage(hWnd, EM_SETSCROLLPOS, 0, (LPARAM)&original_scroll);
+ SendMessage(hWnd, EM_EXSETSEL, 0, (LPARAM)&orig_selection);
+ SendMessage(hWnd, EM_HIDESELECTION, FALSE, 0);
+ SendMessage(hWnd, WM_SETREDRAW, TRUE, 0);
+ RedrawWindow(hWnd, NULL, NULL, RDW_ERASE | RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN);
+ this->idoc->lpVtbl->Undo(this->idoc, tomResume, NULL);
+ free(spans);
+ free(msg);
+ if (!found_private_key)
+ SendMessage(hWnd, SE_PRIVATE_KEY, 0, 0);
+}
+
+static void context_menu(HWND hWnd, INT x, INT y)
+{
+ GETTEXTLENGTHEX gettextlengthex = {
+ .flags = GTL_DEFAULT,
+ .codepage = CP_ACP
+ };
+ /* This disturbing hack grabs the system edit menu normally used for the EDIT control. */
+ HMENU popup, menu = LoadMenuW(GetModuleHandleW(L"comctl32.dll"), MAKEINTRESOURCEW(1));
+ CHARRANGE selection = { 0 };
+ bool has_selection, can_selectall, can_undo, can_paste;
+ UINT cmd;
+
+ if (!menu)
+ return;
+
+ SendMessage(hWnd, EM_EXGETSEL, 0, (LPARAM)&selection);
+ has_selection = selection.cpMax - selection.cpMin;
+ can_selectall = selection.cpMin || (selection.cpMax < SendMessage(hWnd, EM_GETTEXTLENGTHEX, (WPARAM)&gettextlengthex, 0));
+ can_undo = SendMessage(hWnd, EM_CANUNDO, 0, 0);
+ can_paste = SendMessage(hWnd, EM_CANPASTE, CF_TEXT, 0);
+
+ popup = GetSubMenu(menu, 0);
+ EnableMenuItem(popup, WM_UNDO, MF_BYCOMMAND | (can_undo ? MF_ENABLED : MF_GRAYED));
+ EnableMenuItem(popup, WM_CUT, MF_BYCOMMAND | (has_selection ? MF_ENABLED : MF_GRAYED));
+ EnableMenuItem(popup, WM_COPY, MF_BYCOMMAND | (has_selection ? MF_ENABLED : MF_GRAYED));
+ EnableMenuItem(popup, WM_PASTE, MF_BYCOMMAND | (can_paste ? MF_ENABLED : MF_GRAYED));
+ EnableMenuItem(popup, WM_CLEAR, MF_BYCOMMAND | (has_selection ? MF_ENABLED : MF_GRAYED));
+ EnableMenuItem(popup, EM_SETSEL, MF_BYCOMMAND | (can_selectall ? MF_ENABLED : MF_GRAYED));
+
+ /* Delete items that we don't handle. */
+ for (int ctl = GetMenuItemCount(popup) - 1; ctl >= 0; --ctl) {
+ MENUITEMINFOW menu_item = {
+ .cbSize = sizeof(MENUITEMINFOW),
+ .fMask = MIIM_FTYPE | MIIM_ID
+ };
+ if (!GetMenuItemInfoW(popup, ctl, MF_BYPOSITION, &menu_item))
+ continue;
+ if (menu_item.fType & MFT_SEPARATOR)
+ continue;
+ switch (menu_item.wID) {
+ case WM_UNDO:
+ case WM_CUT:
+ case WM_COPY:
+ case WM_PASTE:
+ case WM_CLEAR:
+ case EM_SETSEL:
+ continue;
+ }
+ DeleteMenu(popup, ctl, MF_BYPOSITION);
+ }
+ /* Delete trailing and adjacent separators. */
+ for (int ctl = GetMenuItemCount(popup) - 1, end = true; ctl >= 0; --ctl) {
+ MENUITEMINFOW menu_item = {
+ .cbSize = sizeof(MENUITEMINFOW),
+ .fMask = MIIM_FTYPE
+ };
+ if (!GetMenuItemInfoW(popup, ctl, MF_BYPOSITION, &menu_item))
+ continue;
+ if (!(menu_item.fType & MFT_SEPARATOR)) {
+ end = false;
+ continue;
+ }
+ if (!end && ctl) {
+ if (!GetMenuItemInfoW(popup, ctl - 1, MF_BYPOSITION, &menu_item))
+ continue;
+ if (!(menu_item.fType & MFT_SEPARATOR))
+ continue;
+ }
+ DeleteMenu(popup, ctl, MF_BYPOSITION);
+ }
+
+ if (x == -1 && y == -1) {
+ RECT rect;
+ GetWindowRect(hWnd, &rect);
+ x = rect.left + (rect.right - rect.left) / 2;
+ y = rect.top + (rect.bottom - rect.top) / 2;
+ }
+
+ if (GetFocus() != hWnd)
+ SetFocus(hWnd);
+
+ cmd = TrackPopupMenu(popup, TPM_LEFTALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD | TPM_NONOTIFY, x, y, 0, hWnd, NULL);
+ if (cmd)
+ SendMessage(hWnd, cmd, 0, cmd == EM_SETSEL ? -1 : 0);
+
+ DestroyMenu(menu);
+}
+
+static LRESULT CALLBACK child_proc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
+{
+ switch (Msg) {
+ case WM_CREATE: {
+ struct syntaxedit_data *this = calloc(1, sizeof(*this));
+ assert(this);
+ SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)this);
+ SendMessage(hWnd, EM_GETOLEINTERFACE, 0, (LPARAM)&this->irich);
+ assert(this->irich);
+ this->irich->lpVtbl->QueryInterface(this->irich, &IID_ITextDocument, (void **)&this->idoc);
+ assert(this->idoc);
+ SendMessage(hWnd, EM_SETEVENTMASK, 0, ENM_CHANGE);
+ SendMessage(hWnd, EM_SETTEXTMODE, TM_SINGLECODEPAGE, 0);
+ break;
+ }
+ case WM_DESTROY: {
+ struct syntaxedit_data *this = (struct syntaxedit_data *)GetWindowLongPtr(hWnd, GWLP_USERDATA);
+ this->idoc->lpVtbl->Release(this->idoc);
+ this->irich->lpVtbl->Release(this->irich);
+ free(this);
+ }
+ case WM_SETTEXT: {
+ LRESULT ret = parent_proc(hWnd, Msg, wParam, lParam);
+ highlight_text(hWnd);
+ SendMessage(hWnd, EM_EMPTYUNDOBUFFER, 0, 0);
+ return ret;
+ }
+ case WM_REFLECT + WM_COMMAND:
+ case WM_COMMAND:
+ case WM_REFLECT + WM_NOTIFY:
+ case WM_NOTIFY:
+ switch (HIWORD(wParam)) {
+ case EN_CHANGE:
+ highlight_text(hWnd);
+ break;
+ }
+ break;
+ case WM_PASTE:
+ SendMessage(hWnd, EM_PASTESPECIAL, CF_TEXT, 0);
+ return 0;
+ case WM_KEYDOWN:
+ if (!(GetKeyState(VK_CONTROL) & 0x8000))
+ break;
+ switch (LOWORD(wParam)) {
+ case 'V':
+ SendMessage(hWnd, EM_PASTESPECIAL, CF_TEXT, 0);
+ return 0;
+ }
+ break;
+ case WM_CONTEXTMENU:
+ context_menu(hWnd, LOWORD(lParam), HIWORD(lParam));
+ return 0;
+ }
+ return parent_proc(hWnd, Msg, wParam, lParam);
+}
+
+bool register_syntax_edit(void)
+{
+ WNDCLASSEXW class = { .cbSize = sizeof(WNDCLASSEXW) };
+ WNDPROC pp;
+ HANDLE lib;
+
+ if (parent_proc)
+ return true;
+
+ lib = LoadLibraryW(L"msftedit.dll");
+ if (!lib)
+ return false;
+
+ if (!GetClassInfoExW(NULL, L"RICHEDIT50W", &class))
+ goto err;
+ pp = class.lpfnWndProc;
+ if (!pp)
+ goto err;
+ class.cbSize = sizeof(WNDCLASSEXW);
+ class.hInstance = GetModuleHandleW(NULL);
+ class.lpszClassName = L"WgQuickSyntaxEdit";
+ class.lpfnWndProc = child_proc;
+ if (!RegisterClassExW(&class))
+ goto err;
+ parent_proc = pp;
+ return true;
+
+err:
+ FreeLibrary(lib);
+ return false;
+}