Skip to content
Closed
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
25 changes: 25 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: ci

on:
pull_request:
push:
branches:
- main

jobs:
test:
name: test on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [windows-latest, macos-latest]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'
- name: Test
run: sh mvnw test
shell: bash
44 changes: 38 additions & 6 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<imageName>mousemaster</imageName>
<mainClass>mousemaster.platform.windows.WindowsMain</mainClass>
<jna.version>5.13.0</jna.version> <!-- Check https://www.graalvm.org/latest/reference-manual/native-image/metadata/ -->
<qtjambi.version>6.8.2</qtjambi.version>
</properties>
<dependencies>
<dependency>
Expand Down Expand Up @@ -53,12 +54,7 @@
<dependency>
<groupId>io.qtjambi</groupId>
<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>
<version>${qtjambi.version}</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
Expand All @@ -80,6 +76,42 @@
</dependencies>

<profiles>
<profile>
<id>windows</id>
<activation>
<os>
<family>windows</family>
</os>
</activation>
<properties>
<mainClass>mousemaster.platform.windows.WindowsMain</mainClass>
</properties>
<dependencies>
<dependency>
<groupId>io.qtjambi</groupId>
<artifactId>qtjambi-native-windows-x64</artifactId>
<version>${qtjambi.version}</version>
</dependency>
</dependencies>
</profile>
<profile>
<id>macos</id>
<activation>
<os>
<family>mac</family>
</os>
</activation>
<properties>
<mainClass>mousemaster.platform.macos.MacosMain</mainClass>
</properties>
<dependencies>
<dependency>
<groupId>io.qtjambi</groupId>
<artifactId>qtjambi-native-macos</artifactId>
<version>${qtjambi.version}</version>
</dependency>
</dependencies>
</profile>
<profile>
<id>native</id>
<build>
Expand Down
87 changes: 87 additions & 0 deletions src/main/java/mousemaster/ApplicationLauncher.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package mousemaster;

import com.sun.jna.Native;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

public final class ApplicationLauncher {

private static final Logger logger = LoggerFactory.getLogger(ApplicationLauncher.class);

@FunctionalInterface
public interface PlatformFactory {
Platform create(ApplicationOptions options) throws Exception;
}

private ApplicationLauncher() {
}

public static void run(String[] args, PlatformFactory platformFactory)
throws InterruptedException, IOException {
ApplicationOptions options = ApplicationOptions.parse(args);
MousemasterApplication.setTempDirectory(options.tempDirectory());
if (options.logLevel() != null)
MousemasterApplication.setLogLevel(options.logLevel());
if (options.logToFile())
MousemasterApplication.enableLogToFile();
Version version = readVersion();
if (options.showVersion()) {
System.out.println("mousemaster v" + version.version() + " (" +
version.commitId() + ")");
return;
}
if (options.graalvmAgentRun()) {
logger.info("--graalvm-agent-run flag found, exiting in 20s");
new Thread(() -> {
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.exit(0);
}).start();
}
Platform platform = createPlatform(platformFactory, options);
logger.info("mousemaster v" + version.version() + " (" + version.commitId() + ")");
if (platform == null)
return;
try {
Native.setCallbackExceptionHandler((c, e) ->
MousemasterApplication.shutdownAfterException(e, platform, true,
options.pauseOnError()));
new Mousemaster(options.configurationPath(), platform).run();
} catch (Throwable e) {
MousemasterApplication.shutdownAfterException(e, platform, false,
options.pauseOnError());
}
}

private static Platform createPlatform(PlatformFactory platformFactory,
ApplicationOptions options) {
try {
return platformFactory.create(options);
} catch (Exception e) {
MousemasterApplication.shutdownAfterException(e, null, false,
options.pauseOnError());
}
return null;
}

private static Version readVersion() throws IOException {
try (InputStream versionInputStream = ApplicationLauncher.class.getClassLoader()
.getResourceAsStream(
"application.properties")) {
Properties versionProp = new Properties();
versionProp.load(versionInputStream);
return new Version(versionProp.getProperty("version"),
versionProp.getProperty("commitId"));
}
}

private record Version(String version, String commitId) {
}
}
1 change: 0 additions & 1 deletion src/main/java/mousemaster/KbdlayoutinfoParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import mousemaster.platform.windows.WindowsVirtualKey;
import org.jsoup.Jsoup;
import org.jsoup.select.Elements;
import org.slf4j.Logger;
Expand Down
49 changes: 30 additions & 19 deletions src/main/java/mousemaster/KeyboardLayout.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

import com.google.gson.*;
import com.google.gson.reflect.TypeToken;
import mousemaster.platform.windows.WindowsVirtualKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.InputStreamReader;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -74,6 +74,9 @@ public record KeyboardLayoutKey(int scanCode, WindowsVirtualKey virtualKey, Key
private final String driverName;
private final String shortName;
private final List<KeyboardLayoutKey> keys;
private transient Map<Integer, Key> keyByScanCode;
private transient Map<WindowsVirtualKey, Key> keyByVirtualKey;
private transient Map<Key, KeyboardLayoutKey> layoutKeyByKey;

public KeyboardLayout(String identifier, String displayName, String driverName,
String shortName, List<KeyboardLayoutKey> keys) {
Expand Down Expand Up @@ -109,35 +112,43 @@ public boolean containsKey(Key key) {
}

public Key keyFromScanCode(int scanCode) {
for (KeyboardLayoutKey keyboardLayoutKey : keys) {
if (keyboardLayoutKey.scanCode == scanCode)
return keyboardLayoutKey.key();
}
return null;
buildLookupMaps();
return keyByScanCode.get(scanCode);
}

public Key keyFromVirtualKey(WindowsVirtualKey virtualKey) {
for (KeyboardLayoutKey keyboardLayoutKey : keys) {
if (keyboardLayoutKey.virtualKey == virtualKey)
return keyboardLayoutKey.key();
}
return null;
buildLookupMaps();
return keyByVirtualKey.get(virtualKey);
}

public int scanCode(Key key) {
for (KeyboardLayoutKey keyboardLayoutKey : keys) {
if (keyboardLayoutKey.key.equals(key))
return keyboardLayoutKey.scanCode();
}
return -1;
buildLookupMaps();
KeyboardLayoutKey layoutKey = layoutKeyByKey.get(key);
return layoutKey == null ? -1 : layoutKey.scanCode();
}

public WindowsVirtualKey virtualKey(Key key) {
buildLookupMaps();
KeyboardLayoutKey layoutKey = layoutKeyByKey.get(key);
return layoutKey == null ? null : layoutKey.virtualKey();
}

private void buildLookupMaps() {
if (layoutKeyByKey != null)
return;
Map<Integer, Key> byScanCode = new HashMap<>();
Map<WindowsVirtualKey, Key> byVirtualKey = new HashMap<>();
Map<Key, KeyboardLayoutKey> byKey = new HashMap<>();
for (KeyboardLayoutKey keyboardLayoutKey : keys) {
if (keyboardLayoutKey.key.equals(key))
return keyboardLayoutKey.virtualKey();
byScanCode.putIfAbsent(keyboardLayoutKey.scanCode(), keyboardLayoutKey.key());
if (keyboardLayoutKey.virtualKey() != null)
byVirtualKey.putIfAbsent(keyboardLayoutKey.virtualKey(),
keyboardLayoutKey.key());
byKey.putIfAbsent(keyboardLayoutKey.key(), keyboardLayoutKey);
}
return null;
keyByScanCode = Collections.unmodifiableMap(byScanCode);
keyByVirtualKey = Collections.unmodifiableMap(byVirtualKey);
layoutKeyByKey = Collections.unmodifiableMap(byKey);
}

@Override
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/mousemaster/Mousemaster.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public Mousemaster(Path configurationPath, Platform platform) throws IOException
this.configurationPath = configurationPath;
this.platform = platform;
this.activeKeyboardLayout = platform.activeKeyboardLayout();
QtManager.initialize();
QtManager.initialize(platform.qtDeploymentStrategy());
loadConfiguration(true);
watchService = FileSystems.getDefault().newWatchService();
configurationPath.toAbsolutePath()
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/mousemaster/Platform.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ void reset(MouseManager mouseManager, KeyboardManager keyboardManager,

void shutdown();

QtManager.DeploymentStrategy qtDeploymentStrategy();

KeyRegurgitator keyRegurgitator();

Clock clock();
Expand Down
33 changes: 30 additions & 3 deletions src/main/java/mousemaster/QtManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@
public class QtManager {

private static final Logger logger = LoggerFactory.getLogger(QtManager.class.getName());
private static boolean initialized;

public enum DeploymentStrategy {
WINDOWS_EXTRACTED_RESOURCES,
QTJAMBI_DEFAULT
}

private static final List<String> windowsResourcesPaths = List.of(
"qt/bin/Qt6Core.dll",
Expand Down Expand Up @@ -46,7 +52,15 @@ public class QtManager {
"qt-msvcp/msvcp140_2.dll"
);

public static void initialize() throws IOException {
public static void initialize(DeploymentStrategy deploymentStrategy) throws IOException {
switch (deploymentStrategy) {
case WINDOWS_EXTRACTED_RESOURCES -> initializeWindows();
case QTJAMBI_DEFAULT -> initializeDefault();
}
initialized = true;
}

private static void initializeWindows() throws IOException {
File extractDirectory = createExtractDirectory(
MousemasterApplication.tempDirectory);
for (String resourcesPath : windowsResourcesPaths) {
Expand Down Expand Up @@ -88,6 +102,15 @@ public static void initialize() throws IOException {
// QApplication.initialize(new String[] { });
}

private static void initializeDefault() {
// Used by macOS and other platforms where Qt Jambi native artifacts are
// loaded from the normal dependency/deployment path instead of extracted
// Windows DLL resources. macOS launchers must still provide
// -XstartOnFirstThread so AppKit/Qt runs on the process first thread.
QtUtilities.jambiDeploymentDir();
QApplication.initialize(new String[] {});
}

private static void extractResourceFile(String resourcesPath, Path extractPath)
throws IOException {
try (InputStream inputStream = MousemasterApplication.class.getClassLoader().getResourceAsStream(
Expand All @@ -104,11 +127,15 @@ private static void extractResourceFile(String resourcesPath, Path extractPath)
}

public static void stop() {
QApplication.shutdown();
if (initialized) {
QApplication.shutdown();
initialized = false;
}
}

public static void processEvents() {
QApplication.processEvents();
if (initialized)
QApplication.processEvents();
}

private static File createExtractDirectory(String tempDirectory) throws IOException {
Expand Down
Loading
Loading