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
78 changes: 78 additions & 0 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

80 changes: 80 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
{
description = "Development environment for Mousemaster - Linux port";

inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-26.05";
# nixos-24.11 ships Qt 6.8.x, matching QtJambi 6.8.2.
# QtJambi does a hard version check and refuses Qt 6.9+.
nixpkgs-qt68.url = "github:nixos/nixpkgs/nixos-24.11";
flake-utils.url = "github:numtide/flake-utils";
};

outputs = { self, nixpkgs, nixpkgs-qt68, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs { inherit system; };
pkgs68 = import nixpkgs-qt68 { inherit system; };
in {
devShells.default = pkgs.mkShell {
packages = with pkgs; [
# Java development
jdk21
maven

# X11 libs loaded by JNA at runtime
libx11
libxrandr
libxtst # XTest extension - for key/mouse injection
libxi
libxext
libxrender
libxfixes

# Qt 6.8.x runtime — must match QtJambi 6.8.2 (from nixos-24.11)
pkgs68.qt6.qtbase

# System libs that Qt 6 and the bundled QtJambi .so files will dlopen
libxcb
xcbutil
xcbutilimage
xcbutilkeysyms
xcbutilrenderutil
xcbutilwm
libxkbcommon
fontconfig
freetype
mesa # provides libGL / libEGL

wayland # libwayland-client.so, for the Wayland virtual-pointer JNA bindings
];

shellHook = ''
export JAVA_HOME="${pkgs.jdk21}"
export LD_LIBRARY_PATH="${pkgs68.qt6.qtbase}/lib:${pkgs.lib.makeLibraryPath (with pkgs; [
libx11
libxrandr
libxtst
libxi
libxext
libxrender
libxfixes
libxcb
xcbutil
xcbutilimage
xcbutilkeysyms
xcbutilrenderutil
xcbutilwm
libxkbcommon
fontconfig
freetype
mesa
wayland
])}:$LD_LIBRARY_PATH"
export QT_QPA_PLATFORM_PLUGIN_PATH="${pkgs68.qt6.qtbase}/lib/qt-6/plugins/platforms"
echo "Mousemaster dev environment ready"
echo "Java: $(java -version 2>&1 | head -n1)"
echo "Maven: $(mvn -version 2>&1 | head -n1)"
'';
};
});
}
Empty file modified mvnw
100644 → 100755
Empty file.
38 changes: 32 additions & 6 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,6 @@
<artifactId>qtjambi</artifactId>
<version>6.8.2</version>
</dependency>
<dependency>
<groupId>io.qtjambi</groupId>
<artifactId>qtjambi-native-windows-x64</artifactId>
<version>6.8.2</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
Expand All @@ -80,6 +75,35 @@
</dependencies>

<profiles>
<profile>
<id>windows</id>
<activation>
<os><family>windows</family></os>
</activation>
<dependencies>
<dependency>
<groupId>io.qtjambi</groupId>
<artifactId>qtjambi-native-windows-x64</artifactId>
<version>6.8.2</version>
</dependency>
</dependencies>
</profile>
<profile>
<id>linux</id>
<activation>
<os><name>Linux</name></os>
</activation>
<properties>
<mainClass>mousemaster.platform.linux.LinuxMain</mainClass>
</properties>
<dependencies>
<dependency>
<groupId>io.qtjambi</groupId>
<artifactId>qtjambi-native-linux-x64</artifactId>
<version>6.8.2</version>
</dependency>
</dependencies>
</profile>
<profile>
<id>native</id>
<build>
Expand Down Expand Up @@ -161,7 +185,9 @@
<directory>src/main/resources</directory>
<filtering>true</filtering>
<excludes>
<exclude>**/*.dll</exclude> <!-- Exclude DLLs from filtering. -->
<exclude>**/*.dll</exclude>
<exclude>**/*.so</exclude>
<exclude>**/*.so.*</exclude>
</excludes>
</resource>
</resources>
Expand Down
35 changes: 22 additions & 13 deletions src/main/java/mousemaster/ComboWatcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,14 @@ public class ComboWatcher {
private final HintManager hintManager;
private final ActiveAppFinder activeAppFinder;
private final Clock clock;
private final Set<Key> pressedComboPreconditionKeys;
private final boolean logRedactKeys;
private final Set<Key> unpressedComboPreconditionKeys;
// TODO: this class was modified in commits c85df26/2ad3808 (plus this session,
// uncommitted) to fix Linux key masking. Shared, non-platform code — confirm
// safe/valid here vs. Linux-specific before merge.
private final Map<Mode, Set<Key>> pressedPreconditionKeysByMode;
private final Map<Mode, Set<Key>> unpressedPreconditionKeysByMode;
private Set<Key> currentModePressedPreconditionKeys;
private Set<Key> currentModeUnpressedPreconditionKeys;
private List<ComboListener> comboListeners;
private List<ModeListener> modeListeners;
private Mode baseMode;
Expand Down Expand Up @@ -160,17 +163,12 @@ static Map<Mode, Integer> comboPreparationMinRetainEventCountByMode(ModeMap mode
public ComboWatcher(CommandRunner commandRunner, HintManager hintManager,
ActiveAppFinder activeAppFinder,
Clock clock,
Set<Key> unpressedComboPreconditionKeys,
Set<Key> pressedComboPreconditionKeys, boolean logRedactKeys,
boolean logRedactKeys,
ModeMap modeMap) {
this.commandRunner = commandRunner;
this.hintManager = hintManager;
this.activeAppFinder = activeAppFinder;
this.clock = clock;
this.unpressedComboPreconditionKeys =
unpressedComboPreconditionKeys;
this.pressedComboPreconditionKeys =
pressedComboPreconditionKeys;
this.logRedactKeys = logRedactKeys;
this.comboPreparationRetainDurationByMode = comboPreparationRetainDurationByMode(modeMap);
this.comboPreparationMinRetainEventCountByMode = comboPreparationMinRetainEventCountByMode(modeMap);
Expand All @@ -186,17 +184,24 @@ public ComboWatcher(CommandRunner commandRunner, HintManager hintManager,
}
this.comboPreparation = ComboPreparation.empty();
Map<Mode, Set<Key>> preconditionKeysByMode = new HashMap<>();
Map<Mode, Set<Key>> unpressedPreconditionKeysByMode = new HashMap<>();
for (Mode mode : modeMap.modes()) {
Set<Key> keys = new HashSet<>();
Set<Key> unpressedKeys = new HashSet<>();
for (Combo combo : mode.comboMap().commandsByCombo().keySet()) {
keys.addAll(combo.precondition()
.keyPrecondition()
.pressedKeyPrecondition()
.allKeys());
unpressedKeys.addAll(combo.precondition()
.keyPrecondition()
.unpressedKeySet());
}
preconditionKeysByMode.put(mode, keys);
unpressedPreconditionKeysByMode.put(mode, unpressedKeys);
}
this.pressedPreconditionKeysByMode = preconditionKeysByMode;
this.unpressedPreconditionKeysByMode = unpressedPreconditionKeysByMode;
}

public void setComboListeners(List<ComboListener> comboListeners) {
Expand Down Expand Up @@ -489,9 +494,9 @@ public PressKeyEventProcessingSet keyEvent(KeyEvent event) {
}
modeJustTimedOut = false;
boolean isUnpressedComboPreconditionKey =
unpressedComboPreconditionKeys.contains(event.key());
currentModeUnpressedPreconditionKeys.contains(event.key());
boolean isPressedComboPreconditionKey =
pressedComboPreconditionKeys.contains(event.key());
currentModePressedPreconditionKeys.contains(event.key());
boolean isComboPreconditionKey =
isUnpressedComboPreconditionKey ||
isPressedComboPreconditionKey;
Expand Down Expand Up @@ -601,8 +606,10 @@ public PressKeyEventProcessingSet keyEvent(KeyEvent event) {
if (isIgnoredByLeadingWait)
processing = PressKeyEventProcessing.ignoredByLeadingWait(leadingWaitEatsEvents);
else // isComboPreconditionKey must be true
// Eaten speculatively; regurgitated on release if no combo ends up
// using it (see KeyboardManager's eatenKeys handling).
processing = isPressedComboPreconditionKey ?
PressKeyEventProcessing.partOfPressedComboPreconditionOnly() :
PressKeyEventProcessing.partOfPressedComboPreconditionOnly(true) :
PressKeyEventProcessing.partOfUnpressedComboPreconditionOnly();
processingSet = new PressKeyEventProcessingSet(
new HashMap<>(Map.of(PressKeyEventProcessingSet.dummyCombo, processing)),
Expand Down Expand Up @@ -852,8 +859,8 @@ else if (match.lastEventAbsorbedByWait()
comboPreparationBreaker);
// This processingByCombo does not need to have entries about
// non-combo sequences (i.e. combo preconditions).
// That is because preconditions are managed by the caller (keyEvent)
// which checks across all modes, not just the current one. (isComboPreconditionKey)
// That is because preconditions are managed by the caller (keyEvent),
// scoped to the current mode's precondition keys. (isComboPreconditionKey)
processingByCombo.put(combo, processing);
matchByCombo.put(combo, match);
}
Expand Down Expand Up @@ -1405,6 +1412,8 @@ public void modeChanged(Mode newMode) {
activeMutations.clear();
currentModePressedPreconditionKeys =
pressedPreconditionKeysByMode.getOrDefault(newMode, Set.of());
currentModeUnpressedPreconditionKeys =
unpressedPreconditionKeysByMode.getOrDefault(newMode, Set.of());
computePreconditionOnlyByPropertyPath();
if (!refreshPreconditionOnlyMutations())
notifyMutatedMode();
Expand Down
12 changes: 6 additions & 6 deletions src/main/java/mousemaster/GridManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,10 @@ private void moveGrid(int deltaX, int deltaY) {
// We want the grid center in screen.
Screen activeScreen = screenManager.activeScreen();
gridCenterX = Math.max(activeScreen.rectangle().x(), Math.min(
activeScreen.rectangle().x() + activeScreen.rectangle().width(),
activeScreen.rectangle().x() + activeScreen.rectangle().width() - 1,
gridCenterX));
gridCenterY = Math.max(activeScreen.rectangle().y(), Math.min(
activeScreen.rectangle().y() + activeScreen.rectangle().height(),
activeScreen.rectangle().y() + activeScreen.rectangle().height() - 1,
gridCenterY));
x = gridCenterX - grid.width() / 2;
y = gridCenterY - grid.height() / 2;
Expand Down Expand Up @@ -188,10 +188,10 @@ private void snap(boolean horizontal, boolean forward) {
if (screenManager.screenContaining(x, y) == null) {
Screen activeScreen = screenManager.activeScreen();
x = Math.max(activeScreen.rectangle().x(), Math.min(
activeScreen.rectangle().x() + activeScreen.rectangle().width(),
activeScreen.rectangle().x() + activeScreen.rectangle().width() - 1,
x));
y = Math.max(activeScreen.rectangle().y(), Math.min(
activeScreen.rectangle().y() + activeScreen.rectangle().height(),
activeScreen.rectangle().y() + activeScreen.rectangle().height() - 1,
y));
}
moveMouseTo(x, y);
Expand All @@ -202,7 +202,7 @@ private int mouseColumnX(int mouseColumn, int cellWidth) {
if (mouseColumn <= 0)
x = grid.x();
else if (mouseColumn >= grid.columnCount())
x = grid.x() + grid.width();
x = grid.x() + grid.width() - 1;
else
x = grid.x() + mouseColumn * cellWidth;
return x;
Expand All @@ -213,7 +213,7 @@ private int mouseRowY(int mouseRow, int cellHeight) {
if (mouseRow <= 0)
y = grid.y();
else if (mouseRow >= grid.rowCount())
y = grid.y() + grid.height();
y = grid.y() + grid.height() - 1;
else
y = grid.y() + mouseRow * cellHeight;
return y;
Expand Down
Loading