diff --git a/.gitignore b/.gitignore index 2179c9b..b00a5cc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,2 @@ -*.obj -*.exe -*.res -*.o -*.pdb +build/ sign.bat diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..9243357 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 3.20) +project(show C) + +# Much of the following is to get as small of a binary as possible; CMake loves to insert default flags for us. +foreach(var CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE CMAKE_C_FLAGS_RELWITHDEBINFO CMAKE_C_FLAGS_MINSIZEREL CMAKE_EXE_LINKER_FLAGS CMAKE_EXE_LINKER_FLAGS_DEBUG CMAKE_EXE_LINKER_FLAGS_RELEASE CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO CMAKE_EXE_LINKER_FLAGS_MINSIZEREL) + set(${var} "" CACHE STRING "" FORCE) +endforeach() +add_link_options(/MANIFEST:NO /DYNAMICBASE:NO /NXCOMPAT:NO /SAFESEH:NO /NODEFAULTLIB /ENTRY:main) +add_compile_options(/GS- /GF /Os /O1 /DNDEBUG /TC) + +enable_language(RC) +set_source_files_properties(textbox.rc PROPERTIES LANGUAGE RC) +add_executable(show show.c textbox.rc) +target_link_libraries(show advapi32 comdlg32 comctl32 kernel32 user32) diff --git a/_build_msvc22.bat b/_build_msvc22.bat deleted file mode 100644 index 0df19a1..0000000 --- a/_build_msvc22.bat +++ /dev/null @@ -1,5 +0,0 @@ -@echo off -call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat">nul -rc /nologo textbox.rc -cl /nologo /GS- /GF /Os /O1 /DNDEBUG /TC show.c /link /nodefaultlib /entry:main textbox.res advapi32.lib comdlg32.lib kernel32.lib user32.lib -pause diff --git a/_build_msvc8.bat b/_build_msvc8.bat deleted file mode 100644 index 2ffd318..0000000 --- a/_build_msvc8.bat +++ /dev/null @@ -1,5 +0,0 @@ -@echo off -call "C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\vcvarsall.bat" -rc textbox.rc -cl /nologo /GS- /GF /Os /O1 /DNDEBUG /DTXT_NOBEEPS /Tp show.c textbox.res advapi32.lib comdlg32.lib kernel32.lib user32.lib /link /nodefaultlib /entry:main -pause diff --git a/readme.md b/readme.md index 066ec20..91a95f2 100644 --- a/readme.md +++ b/readme.md @@ -23,9 +23,15 @@ The main thing to warn about here is that for the first iteration of this progra I've done my very best under the circumstances to support UTF16, UTF8, system codepage and windows-1252 files for reading if they are piped in to the program. I do not know if this will be sufficient in all cases, please let me know if you find a plain text file that easily opens in your text editor but can't be successfully piped to the program. ## Building -If you wish to build this yourself, you can run _build_msvc8.bat or _build_msvc19.bat if you have those versions of Visual Studio installed. If you have a different version of Visual Studio, you will need to either remove or alter the "call *" line at the top of the batch script to point to your local version of vcvarsXX.bat. I am fully aware that this building situation probably isn't convenient for anyone but me, and I apologize for that. First my c/c++ experience does not yet include build systems, it will at some point. Second, due to a side-goal of making at least the first version of this project as small as possible, I rely on Visual Studio's ability to uncomplainingly run with no c runtime library which seems to be far easier to get working than with other compilers (I spent a couple hours trying with mixed success). As my programming knowledge continues to evolve, you can rest assured these primative building steps for this and any other projects of mine will become much more mature and will conform to accepted standards. +CMake is used as the build system for this program, targeting MSVC 2022. It's possible that older versions of Visual Studio will work as well, but that seemingly broke the ability to make raw COM calls from pure C from some basic testing. -The one other thing to mention regarding building is this program's one quite rediculis conditional define. Basically, I'm using a windows rich edit control to show the piped text. This is good because it means control+a to select all works on new versions of windows without shipping a .manifest with the app, there is some support for finding text built in to this control, just generally it's a much more full featured edit box. The one annoyance however is the error beeps when you attempt to arrow past the beginning or end of the text field (you can see the same in wordpad). In one sense they make the text field slightly more accessible (you can tell instantly if you're at the border of the field), but new versions of windows in particular play a sound that's like 2 seconds long, which is a very minor annoyance (at least for me). This caused me to look up how to disable this feature of rich edit controls, and to my horror I found out that the only way to acomplish this was via a COM call! Being frank, I wasn't ready to learn the nastyness of making COM calls in pure c for something like this, so instead made the minorly painful decision to pollute my c code with a little bit of c++ to make the comm call, then make it a conditional define so that the program can still compile in pure c if wanted. Though I'll probably do it sometime myself at some point, if someone wishes to submit a pull request with a pure C version of the disable_richedit_beeps function, I'd be much abliged! Just remember that if the TXT_NOBEEPS macro is defined, you must then make sure your compiler is set to compile as C++. +After cloning the repository, simply running: +``` +mkdir build && cd build +cmake .. && cmake --build . +``` + +will give you a fully functional and tiny show.exe in the build directory. ## So much code for such a simple program, why? Trust me, I'm well aware that I have written this program at a rediculisly low level, it could probably be spun up in 40 lines of python and not much more c# or something. However, I was already interested in seeing how small of an executable Visual Studio could generate without a c runtime library, and my idea was to create a simple program. Indeed, the initial version of this thing was less than 90 lines of code, contained 3 functions, and could have possibly been made even smaller. But then I decided to add a find dialog which added another 50 odd lines, then decided to throw in a save feature as well so there's another 50ish, then I decided right before publishing the project that I wanted to support more text file encodings if someone pipes a file to the program, that was another chunk of more than 50 lines of code. In the end, I ended up learning a lot more than I set out to during this development process, however have happily reached my side goal utterly as compiling with vs2008 produces an executable that's only 6.5kb! The ssl signing on the official release I provide shoots it up to 12k instead. All things considered, in the end I'm not unhappy. It took literally days to develop this, there were some painful moments where I was clearly reminded how much easier this would have been had I done this at a higher level, but certainly the fun and challenge made it well worth it for me. If non-c-programmers have ideas for this thing and want to contribute, I may indeed rewrite it at a higher level. For now though, I hope you all get as much use out of this little program as I will get from the knowledge I've collected while making it! diff --git a/show.c b/show.c index 7f2cdf2..fdaabfb 100644 --- a/show.c +++ b/show.c @@ -11,6 +11,7 @@ #include #include #include +#include #include "textbox.h" #define TXTBIT_ALLOWBEEP 0x00000800 @@ -49,6 +50,8 @@ LRESULT CALLBACK edit_control_callback(HWND hwnd, UINT msg, WPARAM wParam, LPARA void disable_richedit_beeps(HMODULE richedit_module, HWND richedit_control); void find(HWND hwnd, int dir); void save(HWND hwnd); +void update_status(HWND dlg); +HWND g_status = NULL; // Globals required for the find dialog. WNDPROC original_edit_control_callback = NULL; // We need to subclass the textbox since the main dialog isn't receiving WM_KEYDOWN for some reason. wchar_t text_to_search[256]; @@ -140,6 +143,10 @@ int main() { HeapFree(process_heap, 0, output); return 1; } + INITCOMMONCONTROLSEX icc; + icc.dwSize = sizeof(icc); + icc.dwICC = ICC_BAR_CLASSES; + InitCommonControlsEx(&icc); dlg = CreateDialog(NULL, MAKEINTRESOURCE(textbox), 0, (DLGPROC)textbox_callback); if(!dlg) { HeapFree(process_heap, 0, output); @@ -150,7 +157,10 @@ int main() { SendMessage(output_box, EM_SETLIMITTEXT, 0, 0); SetWindowText(output_box, output_adjusted); SendMessage(output_box, EM_SETSEL, 0, 0); + SendMessage(output_box, EM_SETEVENTMASK, 0, (LPARAM)(SendMessage(output_box, EM_GETEVENTMASK, 0, 0) | ENM_SELCHANGE)); original_edit_control_callback = (WNDPROC)SetWindowLongPtr(output_box, GWLP_WNDPROC, (LONG_PTR)edit_control_callback); + g_status = CreateStatusWindowW(WS_CHILD | WS_VISIBLE | SBARS_SIZEGRIP, L"", dlg, IDC_STATUS); + update_status(dlg); HeapFree(process_heap, 0, output); // A couple tiny things for the find text dialog. RtlSecureZeroMemory(&text_to_search, sizeof(text_to_search)); @@ -171,6 +181,14 @@ int main() { BOOL CALLBACK textbox_callback(HWND hwnd, UINT message, WPARAM wp, LPARAM lp) { int control, event; switch(message) { + case WM_NOTIFY: { + LPNMHDR nm = (LPNMHDR)lp; + if(nm && nm->idFrom == IDC_TEXT && nm->code == EN_SELCHANGE) { + update_status(hwnd); + return TRUE; + } + break; + } case WM_COMMAND: { control = LOWORD(wp); event = HIWORD(wp); @@ -292,3 +310,31 @@ LRESULT CALLBACK edit_control_callback(HWND hwnd, UINT msg, WPARAM wParam, LPARA } return CallWindowProc(original_edit_control_callback, hwnd, msg, wParam, lParam); } + +void update_status(HWND dlg) { + HWND edit = GetDlgItem(dlg, IDC_TEXT); + CHARRANGE cr; + GETTEXTLENGTHEX gtl; + LONG total_chars; + long caret_pos; + int line_display; + int percent; + wchar_t buf[128]; + RtlSecureZeroMemory(&cr, sizeof(cr)); + SendMessage(edit, EM_EXGETSEL, 0, (LPARAM)&cr); + caret_pos = cr.cpMin; + RtlSecureZeroMemory(>l, sizeof(gtl)); + gtl.flags = GTL_DEFAULT; + gtl.codepage = 1200; // Unicode + total_chars = (LONG)SendMessage(edit, EM_GETTEXTLENGTHEX, (WPARAM)>l, 0); + if (total_chars > 0 && caret_pos > 0) { + percent = (int)(((__int64)caret_pos * 100) / total_chars); + } else { + percent = 0; + } + line_display = (int)SendMessage(edit, EM_LINEFROMCHAR, (WPARAM)caret_pos, 0) + 1; + wsprintfW(buf, L"line %d, character %ld, %d%%", line_display, caret_pos, percent); + if(g_status) { + SendMessage(g_status, SB_SETTEXTW, 0, (LPARAM)buf); + } +} diff --git a/textbox.h b/textbox.h index 30476c7..ba4efeb 100644 --- a/textbox.h +++ b/textbox.h @@ -6,3 +6,4 @@ #define IDL_TEXT 1002 #define IDC_FIND 1003 #define IDC_SAVE 1004 +#define IDC_STATUS 1005