Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 17 additions & 37 deletions .github/workflows/dotnet-desktop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,68 +48,48 @@ jobs:

build:

strategy:
matrix:
configuration: [Debug, Release]

runs-on: windows-latest # For a list of available runner types, refer to
# https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on

env:
Solution_Name: LittleBigMouse # Replace with your solution name, i.e. MyWpfApp.sln.
Solution_Name: LittleBigMouse.sln # Replace with your solution name, i.e. MyWpfApp.sln.
# Test_Project_Path: LittleBigMouse.Ui\LittleBigMouse.Ui.Avalonia # Replace with the path to your test project, i.e. MyWpfApp.Tests\MyWpfApp.Tests.csproj.
Wap_Project_Directory: LittleBigMouse.Ui\LittleBigMouse.Ui.Avalonia # Replace with the Wap project directory relative to the solution, i.e. MyWpfApp.Package.
Wap_Project_Path: LittleBigMouse.Ui\LittleBigMouse.Ui.Avalonia\LittleBigMouse.Ui.Avalonia.csproj # Replace with the path to your Wap project, i.e. MyWpf.App.Package\MyWpfApp.Package.wapproj.

steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0

# Install the .NET Core workload
- name: Install .NET Core
uses: actions/setup-dotnet@v3
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x

# Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild
- name: Setup MSBuild.exe
uses: microsoft/setup-msbuild@v1.0.2

# Execute all unit tests in the solution
# - name: Execute unit tests
# run: dotnet test
uses: microsoft/setup-msbuild@v2

# Restore the application to populate the obj folder with RuntimeIdentifiers
- name: Restore the application
run: msbuild $env:Solution_Name /t:Restore /p:Configuration=$env:Configuration
env:
Configuration: ${{ matrix.configuration }}
run: msbuild $env:Solution_Name /t:Restore /p:Configuration=Release /p:Platform=x64

# Decode the base 64 encoded pfx and save the Signing_Certificate
- name: Decode the pfx
run: |
$pfx_cert_byte = [System.Convert]::FromBase64String("${{ secrets.Base64_Encoded_Pfx }}")
$certificatePath = Join-Path -Path $env:Wap_Project_Directory -ChildPath GitHubActionsWorkflow.pfx
[IO.File]::WriteAllBytes("$certificatePath", $pfx_cert_byte)
- name: Build UI
run: msbuild LittleBigMouse.sln /p:Configuration=Release /p:Platform=x64

# Create the app package by building and packaging the Windows Application Packaging project
- name: Create the app package
run: msbuild $env:Wap_Project_Path /p:Configuration=$env:Configuration /p:UapAppxPackageBuildMode=$env:Appx_Package_Build_Mode /p:AppxBundle=$env:Appx_Bundle /p:PackageCertificateKeyFile=GitHubActionsWorkflow.pfx /p:PackageCertificatePassword=${{ secrets.Pfx_Key }}
env:
Appx_Bundle: Always
Appx_Bundle_Platforms: x86|x64
Appx_Package_Build_Mode: StoreUpload
Configuration: ${{ matrix.configuration }}
- name: Build Hook
run: msbuild LittleBigMouse.Hook\LittleBigMouse.Hook.vcxproj /m /p:Configuration=Release /p:Platform=x64

# Remove the pfx
- name: Remove the pfx
run: Remove-Item -path $env:Wap_Project_Directory\GitHubActionsWorkflow.pfx
- name: Stage Hook next to UI output
run: |
copy LittleBigMouse.Hook\bin\x64\Release\LittleBigMouse.Hook.exe LittleBigMouse.Ui\LittleBigMouse.Ui.Avalonia\bin\x64\Release\net8.0\

# Upload the MSIX package: https://github.com/marketplace/actions/upload-a-build-artifact
- name: Upload build artifacts
uses: actions/upload-artifact@v3
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: MSIX Package
path: ${{ env.Wap_Project_Directory }}\AppPackages
name: LittleBigMouse
path: LittleBigMouse.Ui\LittleBigMouse.Ui.Avalonia\bin\x64\Release\net8.0\
3 changes: 2 additions & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
url = https://github.com/mgth/HLab.Core.git
[submodule "HLab.Avalonia"]
path = HLab.Avalonia
url = https://github.com/mgth/HLab.Avalonia.git
url = https://github.com/thomcuddihy/HLab.Avalonia.git
branch = codex/fix-iconservice-build
2 changes: 1 addition & 1 deletion HLab.Avalonia
98 changes: 98 additions & 0 deletions LittleBigMouse.Hook/Daemon/LittleBigMouseDaemon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ void LittleBigMouseDaemon::Connect()
void LittleBigMouseDaemon::Run(const std::string& path)
{
LOG_TRACE("Daemon started");
StartClipWatcher();
// connect to events
Connect();
LOG_TRACE("Connected");
Expand Down Expand Up @@ -123,6 +124,7 @@ void LittleBigMouseDaemon::Run(const std::string& path)

// disconnect from events
Disconnect();
StopClipWatcher();
}

void LittleBigMouseDaemon::Disconnect()
Expand Down Expand Up @@ -316,6 +318,13 @@ bool LittleBigMouseDaemon::Excluded(const std::string& path) const
// Window focus has changed
void LittleBigMouseDaemon::FocusChanged(const std::string& path)
{
++_focusGen;
{
std::lock_guard<std::mutex> lock(_clipMutex);
_clipPending = true;
}
_cv.notify_one();

if(Excluded(path))
{
LOG_TRACE("<daemon:excluded>");
Expand Down Expand Up @@ -347,6 +356,95 @@ void LittleBigMouseDaemon::FocusChanged(const std::string& path)
_remoteServer->Send("<DaemonMessage><Event>FocusChanged</Event><Payload>"+path+"</Payload></DaemonMessage>\n",nullptr);
}

bool IsCursorClipped() {
RECT clip;
if (!GetClipCursor(&clip))
return false;

RECT virtualRect;
virtualRect.left = GetSystemMetrics(SM_XVIRTUALSCREEN);
virtualRect.top = GetSystemMetrics(SM_YVIRTUALSCREEN);
virtualRect.right = virtualRect.left + GetSystemMetrics(SM_CXVIRTUALSCREEN);
virtualRect.bottom = virtualRect.top + GetSystemMetrics(SM_CYVIRTUALSCREEN);

return !(clip.left == virtualRect.left &&
clip.top == virtualRect.top &&
clip.right == virtualRect.right &&
clip.bottom == virtualRect.bottom);
}

void LittleBigMouseDaemon::HandleClipCheck()
{
if (IsCursorClipped()) {
LOG_TRACE("<daemon:mouseclip>");
if (!_paused)
{
if (_hook && _hook->Hooked())
{
_hook->Unhook();
_paused = true;
LOG_TRACE("<daemon:paused>");
}
}
} else {
if (_paused)
{
if (_hook && !_hook->Hooked())
{
_hook->Hook();
}
_paused = false;

LOG_TRACE("<daemon:wakeup>");
}
}
}


void LittleBigMouseDaemon::StartClipWatcher()
{
_clipThread = std::thread([this]()
{
std::unique_lock<std::mutex> lock(_clipMutex);

while (!_stopping)
{
_cv.wait(lock, [this]
{
return _clipPending || _stopping;
});

if (_stopping)
break;

_clipPending = false;
auto gen = _focusGen.load();

lock.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
lock.lock();

if (gen != _focusGen.load())
continue;

lock.unlock();
HandleClipCheck();
lock.lock();
}
});
}


void LittleBigMouseDaemon::StopClipWatcher()
{
_stopping = true;
_cv.notify_all();

if (_clipThread.joinable())
_clipThread.join();
}


void LittleBigMouseDaemon::ReceiveClientMessage(const std::string& message, RemoteClient* client)
{
if (message.empty())
Expand Down
14 changes: 14 additions & 0 deletions LittleBigMouse.Hook/Daemon/LittleBigMouseDaemon.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#pragma once
#include "Framework.h"

#include <mutex>
#include <atomic>
#include <condition_variable>
#include <string>
#include <vector>

Expand All @@ -26,6 +29,17 @@ class LittleBigMouseDaemon
// paused when current process is excluded
bool _paused = false;

// deferred clip checks
std::thread _clipThread;
std::atomic<bool> _stopping{false};
std::atomic<uint64_t> _focusGen = 0;
std::mutex _clipMutex;
std::condition_variable _cv;
bool _clipPending = false;
void StartClipWatcher();
void StopClipWatcher();
void HandleClipCheck();

void Connect();
void Disconnect();
void ReceiveListenMessage(RemoteClient* client) const;
Expand Down