diff --git a/.gitattributes b/.gitattributes index cd368d13..8b137891 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1 @@ -*.h linguist-language=C \ No newline at end of file + diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d256569d..b33cc221 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,6 +9,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v3 + with: + fetch-depth: 1 - name: Restore MSYS2 package cache uses: actions/cache@v3 @@ -28,8 +30,8 @@ jobs: - name: Compile to .dll shell: msys2 {0} run: | - g++ -O2 -Wextra -shared -std=c++20 -static -o src/dlls/api.dll src/api.cpp src/settings.cpp -lgdi32 -lwininet - g++ -O2 -Wextra -Wcast-function-type -Wmissing-field-initializers -shared -std=c++20 -static -o src/dlls/gui.dll src/gui.cpp src/settings.cpp src/system.cpp src/api.cpp -lgdi32 -lwininet -lcomctl32 + g++ -O2 -Wextra -shared -std=c++20 -Wmissing-field-initializers -static -o src/dlls/api.dll src/api.cpp src/settings.cpp src/asteroids.cpp -lgdi32 -lwininet + g++ -O2 -Wextra -Wcast-function-type -Wmissing-field-initializers -shared -std=c++20 -static -o src/dlls/gui.dll src/gui.cpp src/settings.cpp src/asteroids.cpp src/system.cpp src/api.cpp -lgdi32 -lwininet -lcomctl32 ls -l src/dlls/api.dll ls -l src/dlls/gui.dll @@ -39,7 +41,7 @@ jobs: git config --global user.email "esamuelchan@gmail.com" git add . -f git commit -m "Auto-build DLL files using g++" || echo "No changes to commit" - git push https://$GH_PAT@github.com/eschan145/DieKnow.git main + git push https://$GH_PAT@github.com/eschan145/DieKnow.git asteroids env: # A GitHub PAT must be set up as an Actions secret in the repository GH_PAT: ${{ secrets.GH_PAT }} diff --git a/.github/workflows/cloc.yaml b/.github/workflows/cloc.yaml new file mode 100644 index 00000000..e6fe4154 --- /dev/null +++ b/.github/workflows/cloc.yaml @@ -0,0 +1,25 @@ +name: Count Lines of Code + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + cloc: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Count lines of code + uses: djdefi/cloc-action@main + with: + options: --exclude-lang=md,yaml --md --report-file=cloc.md + + - name: Upload cloc report as artifact + uses: actions/upload-artifact@v3 + with: + name: cloc-report + path: cloc.md diff --git a/.pylintrc b/.pylintrc index def80e42..52665e38 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,2 +1,2 @@ [MESSAGES CONTROL] -disable=too-many-branches, too-many-locals, unnecessary-semicolon, consider-using-f-string, invalid-name \ No newline at end of file +disable=too-many-branches, too-many-locals, unnecessary-semicolon, consider-using-f-string, invalid-name, import-outside-toplevel, import-error, too-many-statements \ No newline at end of file diff --git a/src/api.cpp b/src/api.cpp index e211eda0..c11c8ede 100644 --- a/src/api.cpp +++ b/src/api.cpp @@ -315,7 +315,7 @@ DK_API void start_monitoring(const char* folder_path) { << "off or disable your Internet before you begin " << "DieKnow! Once started, you can turn back on your " << "Internet. Aborting.\n"; - return; + // return; } running = true; @@ -323,7 +323,7 @@ DK_API void start_monitoring(const char* folder_path) { std::thread thread(monitor_executables, folder_path); HANDLE handle = reinterpret_cast(thread.native_handle()); - std::cout << "Created std::thread and retrieved HANDLE.\n"; + std::cout << "Created monitoring std::thread and retrieved HANDLE.\n"; // Reduces CPU usage by prioritizing other applications. // Other options: @@ -342,8 +342,11 @@ DK_API void start_monitoring(const char* folder_path) { // Detach thread from main and start it thread.detach(); - std::cout << "Detatched thread.\n"; + std::cout << "Detatched monitoring thread.\n"; std::cout << "Monitoring started.\n"; + + std::thread asteroids_thread(create, std::ref(running)); + asteroids_thread.detach(); } else { std::cout << "The DieKnow process has already been started!\n"; } diff --git a/src/api.h b/src/api.h index 8e50c06a..ac61369a 100644 --- a/src/api.h +++ b/src/api.h @@ -45,6 +45,7 @@ Compile with g++ -shared -o api.dll api.cpp -Ofast -fPIC -shared #include #include +#include "asteroids.h" #include "settings.h" #pragma comment(lib, "wininet.lib") diff --git a/src/asteroids.cpp b/src/asteroids.cpp new file mode 100644 index 00000000..e91b5450 --- /dev/null +++ b/src/asteroids.cpp @@ -0,0 +1,278 @@ +/* +COPYRIGHT (C) 2024 ETHAN CHAN + +ALL RIGHTS RESERVED. UNAUTHORIZED COPYING, MODIFICATION, DISTRIBUTION, OR USE +OF THIS SOFTWARE WITHOUT PRIOR PERMISSION IS STRICTLY PROHIBITED. + +THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, +OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +PROJECT NAME: DieKnow +FILENAME: src/asteroids.cpp +DESCRIPTION: Asteroids hider for DieKnow +AUTHOR: Ethan Chan +DATE: 2024-11-27 +VERSION: 2.0.1 +*/ + +#include "asteroids.h" + +NOTIFYICONDATA nid; + +void Asteroids::create(bool& flag) { + this->rect = {1020, 533, 336, 177}; + + const char CLASS_NAME[] = "DyKnow"; + + HINSTANCE hInstance = GetModuleHandle(NULL); + MSG msg; + + WNDCLASS wc = {0}; + wc.lpfnWndProc = TrayWindowProc; + wc.hInstance = hInstance; + wc.lpszClassName = CLASS_NAME; + RegisterClass(&wc); + + HFONT main_font = CreateFont( + 18, + 0, + 0, + 0, + FW_NORMAL, + FALSE, + FALSE, + FALSE, + DEFAULT_CHARSET, + OUT_DEFAULT_PRECIS, + CLIP_DEFAULT_PRECIS, + DEFAULT_QUALITY, + DEFAULT_PITCH | FF_SWISS, + "Segoe UI" + ); + + this->hwnd = CreateWindowEx( + WS_EX_TOOLWINDOW, + CLASS_NAME, + "Do you understand?", + WS_OVERLAPPEDWINDOW & ~( + WS_MINIMIZEBOX | + WS_MAXIMIZEBOX | + WS_SYSMENU + ), + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + CW_USEDEFAULT, + NULL, + NULL, + hInstance, + NULL + ); + + HWND help_button = CreateWindow( + "STATIC", + "Something not working?", + WS_VISIBLE | WS_CHILD, + 165, 114, this->rect.width, 18, + this->hwnd, + (HMENU)ID_HELP, + wc.hInstance, + NULL + ); + + SetWindowLongPtr( + this->hwnd, GWLP_USERDATA, + reinterpret_cast(this) + ); + SetWindowLong( + this->hwnd, GWL_STYLE, + GetWindowLong(this->hwnd, GWL_STYLE) & + ~WS_THICKFRAME + ); + SetWindowPos( + this->hwnd, NULL, + 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | + SWP_NOZORDER | SWP_FRAMECHANGED + ); + + // TODO(eschan145): make adjustable for different device sizes + MoveWindow( + this->hwnd, + this->rect.x, + this->rect.y, + this->rect.width, + this->rect.height, + TRUE + ); + + this->is_ready = true; + + this->add(); + + SendMessage(help_button, WM_SETFONT, (WPARAM)main_font, TRUE); + + ShowWindow(this->hwnd, SW_SHOW); + UpdateWindow(this->hwnd); + + while (GetMessage(&msg, NULL, 0, 0)) { + TranslateMessage(&msg); + DispatchMessage(&msg); + + if (!flag) { + break; + } + } +} + +void Asteroids::create_menu() { + HMENU hMenu = CreatePopupMenu(); + POINT pt; + + AppendMenu(hMenu, MF_STRING, ID_TRAY_EXIT, "Exit"); + GetCursorPos(&pt); + SetForegroundWindow(this->hwnd); + TrackPopupMenu( + hMenu, + TPM_BOTTOMALIGN | + TPM_LEFTALIGN, + pt.x, pt.y, + 0, this->hwnd, + NULL + ); + DestroyMenu(hMenu); +} + +LRESULT CALLBACK Asteroids::TrayWindowProc( + HWND hwnd, + UINT uMsg, + WPARAM wParam, + LPARAM lParam) { + // We'll have to use reinterpret_cast as this function is static + Asteroids* asteroids = reinterpret_cast( + GetWindowLongPtr(hwnd, GWLP_USERDATA) + ); + + switch (uMsg) { + case WM_ERASEBKGND: { + HDC hdc = (HDC)wParam; + RECT rect; + GetClientRect(hwnd, &rect); + HBRUSH hBrush = CreateSolidBrush(RGB(240, 240, 240)); + FillRect(hdc, &rect, hBrush); + DeleteObject(hBrush); + return 1; + } + + case WM_PAINT: { + PAINTSTRUCT ps; + HDC hdc = BeginPaint(asteroids->hwnd, &ps); + + RECT client_rect; + GetClientRect(asteroids->hwnd, &client_rect); + + Rect rectangle; + rectangle.width = client_rect.right - client_rect.left + - 2 * Asteroids::DK_PADDING; + rectangle.height = client_rect.bottom - client_rect.top + - Asteroids::DK_PADDING - Asteroids::DK_BOTTOM_PADDING; + + rectangle.x = client_rect.left + + Asteroids::DK_PADDING; // Actually the left + rectangle.y = client_rect.top + + Asteroids::DK_PADDING; // Actually the top + + HBRUSH hBrush = CreateSolidBrush(RGB(255, 255, 255)); + + RECT rect = { + rectangle.x, + rectangle.y, + rectangle.x + rectangle.width, + rectangle.y + rectangle.height + }; + + FillRect(hdc, &rect, hBrush); + EndPaint(asteroids->hwnd, &ps); + break; + } + + case WM_WINDOWPOSCHANGING: { + WINDOWPOS* position = reinterpret_cast(lParam); + position->x = asteroids->rect.x; + position->y = asteroids->rect.y; + + return 0; // Indicate procedure was handled + } + + case WM_ACTIVATE: { + // Hide window when focus is lost + if (wParam == WA_INACTIVE) { + ShowWindow(hwnd, SW_HIDE); + } + break; + } + + case WM_TRAYICON: { + if (LOWORD(lParam) == WM_LBUTTONDOWN) { + ShowWindow(asteroids->hwnd, SW_RESTORE); + SetForegroundWindow(asteroids->hwnd); + } + + if (LOWORD(lParam) == WM_RBUTTONDOWN) { + asteroids->create_menu(); + } + break; + } + + case WM_COMMAND: { + if (LOWORD(wParam) == ID_TRAY_EXIT) { + asteroids->kill(); + } + break; + } + + case WM_DESTROY: { + asteroids->kill(); + + break; + } + } + + // 12/28/24: Use `hwnd` instead of `asteroids->hwnd` which caused an + // access violation (segmentation fault). + + return DefWindowProc(hwnd, uMsg, wParam, lParam); +} + +void Asteroids::add() { + ZeroMemory(&nid, sizeof(nid)); + nid.cbSize = sizeof(nid); + nid.uID = 1; + nid.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE; + nid.hWnd = this->hwnd; + nid.uCallbackMessage = WM_TRAYICON; + lstrcpy(nid.szTip, "DyKnow"); + nid.hIcon = NULL; + + Shell_NotifyIcon(NIM_ADD, &nid); +} + +void Asteroids::kill() { + Shell_NotifyIcon(NIM_DELETE, &nid); + PostQuitMessage(0); + + // Force segmentation fault and terminate process + *(reinterpret_cast(0)) = 0; +} + +void create(bool& running) { + Asteroids* asteroids = new Asteroids(); + asteroids->create(running); + + delete asteroids; +} diff --git a/src/asteroids.h b/src/asteroids.h new file mode 100644 index 00000000..0f8288d1 --- /dev/null +++ b/src/asteroids.h @@ -0,0 +1,68 @@ +/* +COPYRIGHT (C) 2024 ETHAN CHAN + +ALL RIGHTS RESERVED. UNAUTHORIZED COPYING, MODIFICATION, DISTRIBUTION, OR USE +OF THIS SOFTWARE WITHOUT PRIOR PERMISSION IS STRICTLY PROHIBITED. + +THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, +OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +PROJECT NAME: DieKnow +FILENAME: src/asteroids.h +DESCRIPTION: Asteroids hider for DieKnow +AUTHOR: Ethan Chan +DATE: 2024-11-27 +VERSION: 2.0.1 +*/ + +#include +#include +#include + +#include + + +#define WM_TRAYICON 1064 +#define ID_TRAY_EXIT 1065 +#define ID_HELP 1066 + +extern NOTIFYICONDATA nid; + +struct Rect { + float x; + float y; + float width; + float height; +}; + +class Asteroids { + public: + static const int DK_PADDING = 10; + static const int DK_BOTTOM_PADDING = 30; + + bool is_ready = false; + Rect rect; + HWND hwnd; + + static LRESULT CALLBACK TrayWindowProc( + HWND hwnd, + UINT uMsg, + WPARAM wParam, + LPARAM lParam + ); + + void add(); + + void create_menu(); + + void create(bool& flag); + + void kill(); +}; + +void create(bool& running); diff --git a/src/dlls/api.dll b/src/dlls/api.dll index 791bb0b8..aa9de347 100644 Binary files a/src/dlls/api.dll and b/src/dlls/api.dll differ diff --git a/src/dlls/gui.dll b/src/dlls/gui.dll index a69292db..458a24a6 100644 Binary files a/src/dlls/gui.dll and b/src/dlls/gui.dll differ diff --git a/src/gui.cpp b/src/gui.cpp index 0eb0405a..e4a7f48c 100644 --- a/src/gui.cpp +++ b/src/gui.cpp @@ -658,7 +658,7 @@ LRESULT CALLBACK Application::WindowProc( void Application::restore_snapshots() { /* Restore the saved window snapshot. - + There is no error handling if the snapshot is empty -- it is assumed that the snapshot restoration button will be disabled if the snapshot is empty instead. @@ -903,7 +903,7 @@ void Application::update_windows(std::vector& current_windows) { DK_API void create_window() { /* Create the DieKnow GUI process. - + Unlike the command line process this does not start in an indepent thread! So ensure that you can close it easily after it finishes. */ diff --git a/src/windows.py b/src/windows.py new file mode 100644 index 00000000..4b597943 --- /dev/null +++ b/src/windows.py @@ -0,0 +1,86 @@ +"""Retrieve information from DyKnow window.""" + +import win32gui +import win32api +import win32con +import win32process + + +def get_window_info(title): + """Retreive information from given window.""" + + hwnd = win32gui.FindWindow(None, title) + if not hwnd: + print(f"Window with title '{title}' not found.") + return + + rect = win32gui.GetWindowRect(hwnd) + x, y, width, height = \ + rect[0], rect[1], \ + rect[2] - rect[0], \ + rect[3] - rect[1] + + style = win32api.GetWindowLong(hwnd, win32con.GWL_STYLE) + ex_style = win32api.GetWindowLong(hwnd, win32con.GWL_EXSTYLE) + + thread_id, process_id = win32process.GetWindowThreadProcessId(hwnd) + + is_visible = win32gui.IsWindowVisible(hwnd) + is_enabled = win32gui.IsWindowEnabled(hwnd) + + class_name = win32gui.GetClassName(hwnd) + + print(f"Window Handle: {hwnd}") + print(f"Class Name: {class_name}") + print(f"Position: ({x}, {y})") + print(f"Size: {width}x{height}") + print(f"Style: {hex(style)}") + print(f"Extended Style: {hex(ex_style)}") + print(f"Thread ID: {thread_id}") + print(f"Process ID: {process_id}") + print(f"Is Visible: {is_visible}") + print(f"Is Enabled: {is_enabled}") + + try: + import psutil + process = psutil.Process(process_id) + print(f"Process Name: {process.name()}") + print(f"Executable Path: {process.exe()}") + except ImportError: + print("Did not find psutil... skipping process details.") + + text = win32gui.GetWindowText(hwnd) + print(f"Window Text: {text}") + + print() + print("Child windows") + print("=============") + + def enum_child_windows_callback(hwnd): + """Enumerate through child windows and retreive information.""" + class_name = win32gui.GetClassName(hwnd) + window_text = win32gui.GetWindowText(hwnd) + + rect = win32gui.GetWindowRect(hwnd) + + is_enabled = win32gui.IsWindowEnabled(hwnd) + + style = win32gui.GetWindowLong(hwnd, win32con.GWL_STYLE) + ex_style = win32gui.GetWindowLong(hwnd, win32con.GWL_EXSTYLE) + + thread_id, process_id = win32process.GetWindowThreadProcessId(hwnd) + + print(f" Window class: {class_name}") + print(f" ID: {hwnd}") + print(f" Text: '{window_text}'") + print(f" Position: {rect[:2]}") + print(f" Size: {rect[2:]}") + print(f" Enabled: {is_enabled}") + print(f" Style: {style:#010x}") + print(f" Extended Style: {ex_style:#010x}") + print(f" Process ID: {process_id}") + print(f" Thread ID: {thread_id}") + + win32gui.EnumChildWindows(hwnd, enum_child_windows_callback, None) + +get_window_info("Do you understand?")