Merge remote-tracking branch 'origin/main'

This commit is contained in:
Mark Nellemann 2023-08-16 09:53:02 +02:00
commit ed57148d34
24 changed files with 326 additions and 517 deletions

2
.dockerignore Normal file
View file

@ -0,0 +1,2 @@
.git
.gradle

29
.drone.yml Normal file
View file

@ -0,0 +1,29 @@
---
kind: pipeline
name: default
type: docker
#platform:
# os: linux
# arch: amd64
steps:
# - name: test
# image: docker.io/bellsoft/liberica-openjdk-debian:17
# commands:
# - ./gradlew --quiet --no-daemon test
- name: jpackage
image: docker.io/debian:stable
environment:
AUTH_TOKEN: # Gitea access token ENV variable
from_secret: AUTH_TOKEN # Name of DroneCI secret exposed above
commands:
- apt-get update && apt-get install -y dpkg-dev rpm unzip zip curl openjdk-17-jdk
- ./gradlew --no-daemon clean build jpackage
- for file in build/jpackage/*.* ; do curl -s --user "$${AUTH_TOKEN}" --upload-file "$${file}" "https://git.data.coop/api/packages/${DRONE_REPO_OWNER}/generic/${DRONE_REPO_NAME}/${DRONE_TAG}/$(basename $file)" ; done
when:
event:
- tag

View file

@ -1,96 +1,37 @@
# mDNS Explorer # mDNS Explorer
View all multicastDNS devices. View multicastDNS services on your local network.
![mDNS-Explorer](doc/mDNS-Explorer.png)
## Development ## Development
Java SDK version 17 (or later) is required. Java SDK version 17 (or later) is required.
## Native Images Information on how to build and package jftpd:
The native images are built with GraalVM.
Download the Gluon version of GraalVM from https://github.com/gluonhq/graal/releases/tag/gluon-22.1.0.1-Final and extract it locally.
Point the GRAALVM_HOME environment variable to the folder:
```export GRAALVM_HOME="/path/to/gluon-22.1.0.1```
With all requirements setup and on a supported platform, the general idea is to run:
```shell ```shell
./gradlew clean build ./gradlew build jpackage
./gradlew nativeBuild -Ptarget=android # or -Ptarget=ios
./gradlew nativeLink -Ptarget=android
./gradlew nativePackage -Ptarget=android
./gradlew nativeInstall -Ptarget=android
./gradlew nativeRun -Ptarget=android
``` ```
or, in one go for Android: ### Windows
```shell Download and install
./gradlew -Ptarget=android clean build nativeBuild nativeLink nativePackage nativeInstall nativeRun - Microsoft .NET Framework 3.5
``` - Wix Toolset 3.11.2 (or later)
or, in one go for iOS:
```shell
./gradlew -Ptarget=ios clean build nativeBuild nativeLink nativePackage nativeInstall nativeRun
```
### Linux ### Linux
See https://docs.gluonhq.com/#prerequisites_linux TODO
For nativeBuild on Linux, install the following dependencies. docker build . -f docker/Dockerfile.rpm-txt
```shell or
sudo apt install libgtk-3-dev libavformat-dev libavutil-dev libavcodec-dev libasound2-dev libpango1.0-dev libxtst-dev build-essential
```
#### Building for Android (on Linux) docker build . -f docker/Dockerfile.deb-txt
See https://docs.gluonhq.com/#prerequisites_android
Install Android Studio and use sdkmanager to install the following:
- Android SDK Platform 31
- NDK (Side by side)
Point the ANDROID_NDK variable to the installed NDK directory:
```
export ANDROID_NDK="/home/mark/Android/Sdk/ndk/25.2.9519653/"
```
Add the platform-tools to you PATH to use 'adb' command:
```
export PATH="$PATH:/home/mark/Android/Sdk/platform-tools/"
```
Run the 'adb devices' and 'adb usb' to verify your Android networkService is in development mode and allows connections.
### MacOS ### MacOS
See https://docs.gluonhq.com/#platforms_macos TODO
Install the xcode command line tools and an iPhone emulator.
If you have problems with *xcrun* not being able to find *iphoneos*, try:
```shell
sudo xcode-select --switch /Applications/Xcode.app
```
Download and install homebrew, and install the following:
```shell
brew install maven
brew install --HEAD libusbmuxd
brew install --HEAD libimobiledevice
```
#### iOS (on MacOS)
See https://docs.gluonhq.com/#prerequisites_ios

View file

@ -1,49 +1,47 @@
import org.apache.tools.ant.filters.ReplaceTokens
plugins { plugins {
id 'java'
id 'groovy'
id 'application' id 'application'
id 'org.openjfx.javafxplugin' version '0.0.14' id 'org.openjfx.javafxplugin' version '0.0.14'
id "com.github.johnrengelman.shadow" version "8.1.1" id 'com.google.osdetector' version '1.7.3'
id 'com.gluonhq.gluonfx-gradle-plugin' version '1.0.19' id 'org.beryx.jlink' version '2.26.0'
} }
repositories { repositories {
mavenCentral() mavenCentral()
mavenLocal() mavenLocal()
maven {
url 'https://nexus.gluonhq.com/nexus/content/repositories/releases'
}
} }
version = "0.0.1"
mainClassName = "biz.nellemann.mdexpl.App"
application.mainModule = "biz.nellemann.mdexpl"
tasks.withType(JavaCompile).configureEach { tasks.withType(JavaCompile).configureEach {
options.encoding = 'UTF-8' options.encoding = 'UTF-8'
} }
application {
mainModule = 'biz.nellemann.mdexpl'
mainClass = 'biz.nellemann.mdexpl.App'
}
java { java {
sourceCompatibility = JavaVersion.VERSION_17 sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17
modularity.inferModulePath = false toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
} }
/* This is to be able to build with a JDK not bundled with JavaFX */ /* This is to be able to build with a JDK not bundled with JavaFX */
javafx { javafx {
version = '21+' version = '17.0.8'
modules = [ 'javafx.controls', 'javafx.fxml' ] modules = [ 'javafx.controls', 'javafx.fxml' ]
// platform("linux-aarch64") // platform("linux-aarch64")
} }
dependencies { dependencies {
implementation 'com.gluonhq:charm-glisten:6.2.3'
implementation 'com.gluonhq:glisten-afterburner:2.1.0'
implementation 'org.slf4j:slf4j-api:2.0.7' // Logging API implementation 'org.slf4j:slf4j-api:2.0.7' // Logging API
runtimeOnly 'org.slf4j:slf4j-simple:2.0.7' // Logging API runtimeOnly 'org.slf4j:slf4j-simple:2.0.7' // Logging API
implementation 'jakarta.inject:jakarta.inject-api:2.0.1'
implementation 'jakarta.annotation:jakarta.annotation-api:2.1.1'
implementation 'org.jmdns:jmdns:3.5.8' implementation 'org.jmdns:jmdns:3.5.8'
testImplementation 'org.spockframework:spock-core:2.3-groovy-3.0' testImplementation 'org.spockframework:spock-core:2.3-groovy-3.0'
@ -54,66 +52,67 @@ test {
useJUnitPlatform() useJUnitPlatform()
} }
jlink {
gluonfx { forceMerge 'slf4j'
verbose = true
target = project.hasProperty("target") ? project.getProperty("target") : 'host'
//target = 'ios' // Uncomment to enable iOS - see https://docs.gluonhq.com/#prerequisites_ios
//target = 'android' // Uncomment to enable Android - see https://docs.gluonhq.com/#prerequisites_android
attachConfig { options = [
version = "4.0.18" '--strip-debug',
services 'storage', 'display', 'lifecycle', 'statusbar' '--compress', '2',
} '--no-header-files',
'--no-man-pages'
reflectionList = [
"javafx.fxml.FXMLLoader",
"com.gluonhq.charm.glisten.mvc.View",
"com.gluonhq.charm.glisten.control.Icon",
"com.gluonhq.charm.glisten.control.DropdownButton",
"com.gluonhq.charm.glisten.control.BottomNavigation",
"com.gluonhq.charm.glisten.control.BottomNavigationButton",
"biz.nellemann.mdexpl.view.AboutPresenter",
"biz.nellemann.mdexpl.view.MainPresenter", "biz.nellemann.mdexpl.model.MainModel",
"biz.nellemann.mdexpl.service.DiscoveryService",
] ]
compilerArgs = [ launcher {
'-Djava.awt.headless=true' name = 'mDNS-Explorer'
noConsole = true
}
// Only works with Java 14 (and later)
jpackage {
imageName = "mDNS-Explorer"
skipInstaller = true
installerName = "mDNS-Explorer-${osdetector.arch}"
installerOptions += [
'--vendor', 'Nellemann Data',
'--description', 'List mDNS services on your local network.',
'--copyright', 'Mark Nellemann <mark.nellemann@gmail.com>',
'--app-version', project.findProperty('version')
] ]
appIdentifier = 'biz.nellemann.mdexpl' // Requires: https://wixtoolset.org/ to create installer on Windows
if(osdetector.os == 'windows') {
installerType = 'msi'
skipInstaller = false
installerOptions += [
'--win-per-user-install',
'--win-dir-chooser',
'--win-menu',
// '--icon', 'src/main/resources/icon.ico'
]
}
// Requires: xcode-select --install
if(osdetector.os == 'osx') {
installerType = 'dmg'
skipInstaller = false
installerOptions += [
//'--icon', 'src/main/resources/icon.icns'
]
}
// Requires: build-rpm / rpm and dpkg-dev
if(osdetector.os == 'linux') {
skipInstaller = false
installerOptions += [
'--linux-shortcut',
'--linux-menu-group', 'Internet',
'--linux-rpm-license-type', 'APACHE-20',
'--linux-deb-maintainer', 'mark.nellemann@gmail.com',
'--icon', 'src/main/resources/icon.png',
]
}
release {
// Android
appLabel = "mDNS Explorer"
//versionCode = "1"
//versionName = "1.0"
//providedKeyStorePath = ""
//providedKeyStorePassword = ""
//providedKeyAlias = ""
//providedKeyAliasPassword = ""
// iOS
//bundleName = "mDNS Explorer"
//bundleVersion = ""
//bundleShortVersion = ""
//providedSigningIdentity = ""
//providedProvisioningProfile = ""
//skipSigning = true // Will not run or deploy if not signed
} }
} }
shadowJar {
//archiveBaseName.set("vtd-poc-app")
//archiveClassifier.set('all')
archiveVersion.set("${System.env.BITBUCKET_BRANCH ?: 'dev' }")
}
tasks.build.dependsOn tasks.shadowJar
tasks.processResources {
filesMatching('**/configuration.properties') {
filter(ReplaceTokens, tokens: [copyright: '2023', version: System.env.BITBUCKET_BUILD_NUMBER ?: 'development' ])
}
}

BIN
doc/mDNS-Explorer.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

12
docker/Dockerfile.deb-txt Normal file
View file

@ -0,0 +1,12 @@
FROM docker.io/debian:stable AS build
RUN apt-get update && apt-get install -y unzip zip curl dpkg-dev
RUN bash -c "curl -s 'https://get.sdkman.io' | bash"
RUN bash -c "source $HOME/.sdkman/bin/sdkman-init.sh && sdk install java 17.0.8-zulu"
COPY ./src /tmp/prj/src
COPY ./gradle /tmp/prj/gradle
COPY [ "gradlew", "build.gradle", "gradle.properties", "settings.gradle", "/tmp/prj/" ]
WORKDIR /tmp/prj
RUN bash -c "source $HOME/.sdkman/bin/sdkman-init.sh && ./gradlew --no-daemon clean build jpackage"

14
docker/Dockerfile.rpm-txt Normal file
View file

@ -0,0 +1,14 @@
FROM docker.io/almalinux:8 AS build
RUN dnf -y install unzip zip rpm-build
RUN sh -c "curl -s 'https://get.sdkman.io' | bash"
RUN sh -c "source $HOME/.sdkman/bin/sdkman-init.sh && sdk install java 17.0.8-zulu"
COPY ./src /tmp/prj/src
COPY ./gradle /tmp/prj/gradle
COPY [ "gradlew", "build.gradle", "gradle.properties", "settings.gradle", "/tmp/prj/" ]
WORKDIR /tmp/prj
RUN sh -c "source $HOME/.sdkman/bin/sdkman-init.sh && ./gradlew --no-daemon clean build jpackage"
COPY --from=build build/jpackage/* build/

1
gradle.properties Normal file
View file

@ -0,0 +1 @@
version = 1.0.1

View file

@ -1,9 +1,6 @@
pluginManagement { pluginManagement {
repositories { repositories {
mavenLocal() mavenLocal()
maven {
url "https://nexus.gluonhq.com/nexus/content/repositories/releases"
}
gradlePluginPortal() gradlePluginPortal()
} }
} }

View file

@ -1,36 +1,50 @@
package biz.nellemann.mdexpl; package biz.nellemann.mdexpl;
import biz.nellemann.mdexpl.view.AppViewManager;
import com.gluonhq.charm.glisten.application.AppManager;
import com.gluonhq.charm.glisten.visual.Swatch;
import javafx.application.Application; import javafx.application.Application;
import javafx.application.Platform;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.scene.image.Image; import javafx.scene.image.Image;
import javafx.stage.Stage; import javafx.stage.Stage;
import java.util.Objects; import java.awt.Taskbar;
import java.awt.Toolkit;
import java.awt.Taskbar.Feature;
import java.io.IOException;
public class App extends Application { public class App extends Application {
private final AppManager appManager = AppManager.initialize(this::postInit);
@Override @Override
public void init() { public void start(Stage primaryStage) throws IOException {
AppViewManager.registerViewsAndDrawer();
// Make all stages close and the app exit when the primary stage is closed
Platform.setImplicitExit(true);
primaryStage.setOnCloseRequest(e -> {
System.exit(0);
});
// Set icon on the application bar
var appIcon = new Image("/icon.png");
primaryStage.getIcons().add(appIcon);
// Set icon on the taskbar/dock
if (Taskbar.isTaskbarSupported()) {
var taskbar = Taskbar.getTaskbar();
if (taskbar.isSupported(Feature.ICON_IMAGE)) {
final Toolkit defaultToolkit = Toolkit.getDefaultToolkit();
var dockIcon = defaultToolkit.getImage(getClass().getResource("/icon.png"));
taskbar.setIconImage(dockIcon);
} }
@Override
public void start(Stage primaryStage) {
//System.setProperty(com.gluonhq.attach.util.Constants.ATTACH_DEBUG,"true");
appManager.start(primaryStage);
} }
FXMLLoader fxmlLoader = new FXMLLoader(App.class.getResource("main.fxml"));
private void postInit(Scene scene) { Scene scene = new Scene(fxmlLoader.load());
Swatch.GREEN.assignTo(scene); primaryStage.setTitle("mDNS Explorer");
//scene.getStylesheets().add(App.class.getResource("style.css").toExternalForm()); primaryStage.setScene(scene);
((Stage) scene.getWindow()).getIcons().add(new Image(Objects.requireNonNull(App.class.getResourceAsStream("/icon.png")))); primaryStage.show();
} }

View file

@ -0,0 +1,51 @@
package biz.nellemann.mdexpl;
import biz.nellemann.mdexpl.model.NetworkService;
import biz.nellemann.mdexpl.service.DiscoveryService;
import jakarta.inject.Inject;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.ListView;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.ResourceBundle;
public class MainPresenter {
private static final Logger log = LoggerFactory.getLogger(MainPresenter.class);
@FXML
private BorderPane view;
@FXML
private ResourceBundle resources;
@FXML
private ListView<NetworkService> listView;
@FXML
public ListView<String> propertiesList;
@Inject
DiscoveryService discoveryService;
private final ObservableList<NetworkService> devicesList = FXCollections.observableArrayList();
@FXML
public void initialize() {
log.info("initialize()");
listView.setItems(devicesList);
listView.setCellFactory(p -> new NetworkServiceCell());
discoveryService = new DiscoveryService(devicesList);
}
}

View file

@ -1,40 +1,47 @@
package biz.nellemann.mdexpl; package biz.nellemann.mdexpl;
import biz.nellemann.mdexpl.model.NetworkService; import biz.nellemann.mdexpl.model.NetworkService;
import com.gluonhq.attach.util.impl.ClipboardUtils; import javafx.collections.FXCollections;
import com.gluonhq.charm.glisten.control.CharmListCell; import javafx.scene.Node;
import com.gluonhq.charm.glisten.control.ListTile; import javafx.scene.Scene;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.input.ClipboardContent; import javafx.scene.input.ClipboardContent;
import javafx.scene.layout.HBox;
import javafx.scene.shape.Rectangle; import javafx.scene.shape.Rectangle;
import javafx.scene.input.Clipboard; import javafx.scene.input.Clipboard;
import java.util.Optional; public class NetworkServiceCell extends ListCell<NetworkService> {
public class NetworkServiceCell extends CharmListCell<NetworkService> {
private final ListTile tile;
private final Rectangle icon; private final Rectangle icon;
private final Clipboard clipboard; private final Clipboard clipboard;
private final ClipboardContent clipboardContent; private final ClipboardContent clipboardContent;
HBox hBox;
public NetworkServiceCell() { public NetworkServiceCell() {
clipboard = Clipboard.getSystemClipboard(); clipboard = Clipboard.getSystemClipboard();
clipboardContent = new ClipboardContent(); clipboardContent = new ClipboardContent();
this.tile = new ListTile();
//imageView = new ImageView();
//imageView.setFitHeight(15);
//imageView.setFitWidth(25);
//tile.setPrimaryGraphic(imageView);
icon = new Rectangle(); icon = new Rectangle();
icon.setHeight(25); icon.setHeight(25);
icon.setWidth(25); icon.setWidth(25);
tile.setPrimaryGraphic(icon); this.setGraphic(icon);
tile.setOnMouseClicked(e -> {
this.setOnMouseClicked(e -> {
if(itemProperty().get() != null) {
clipboardContent.putString(itemProperty().get().getUrl()); clipboardContent.putString(itemProperty().get().getUrl());
clipboard.setContent(clipboardContent); clipboard.setContent(clipboardContent);
Node source = (Node) e.getSource();
Scene scene = source.getScene();
Object node = scene.lookup("#propertiesList");
if(node instanceof ListView listView) {
listView.setItems(FXCollections.observableArrayList(itemProperty().get().getProperties()));
}
};
}); });
setText(null); setText(null);
} }
@ -43,16 +50,14 @@ public class NetworkServiceCell extends CharmListCell<NetworkService> {
@Override @Override
public void updateItem(NetworkService item, boolean empty) { public void updateItem(NetworkService item, boolean empty) {
super.updateItem(item, empty); super.updateItem(item, empty);
if (item != null && !empty) { if (empty) {
tile.textProperty().setAll(item.getName(), setText(null);
item.getApp() + " (" + item.getSubType() + ") - " + item.getUrl()
);
icon.setFill(item.getColor());
setGraphic(tile);
} else {
setGraphic(null); setGraphic(null);
} else {
setText(item.toString());
icon.setFill(item.getColor());
setGraphic(icon);
} }
} }
} }

View file

@ -10,6 +10,7 @@ import org.slf4j.LoggerFactory;
import javax.jmdns.ServiceEvent; import javax.jmdns.ServiceEvent;
import javax.jmdns.ServiceInfo; import javax.jmdns.ServiceInfo;
import javax.jmdns.ServiceListener; import javax.jmdns.ServiceListener;
import java.util.Iterator;
public class NetworkServiceListener implements ServiceListener { public class NetworkServiceListener implements ServiceListener {
@ -21,7 +22,7 @@ public class NetworkServiceListener implements ServiceListener {
private final Color color; private final Color color;
public NetworkServiceListener(String service, ObservableList<NetworkService> observableList, Color color) { public NetworkServiceListener(String service, ObservableList<NetworkService> observableList, Color color) {
log.info("NetworkServiceListener() - type: {}", service); log.debug("NetworkServiceListener() - type: {}", service);
this.service = service; this.service = service;
this.observableList = observableList; this.observableList = observableList;
this.color = color; this.color = color;
@ -38,15 +39,15 @@ public class NetworkServiceListener implements ServiceListener {
ServiceInfo serviceInfo = event.getInfo(); ServiceInfo serviceInfo = event.getInfo();
if (serviceInfo != null) { if (serviceInfo != null) {
String name = serviceInfo.getName(); String name = serviceInfo.getName();
String url = serviceInfo.getURLs()[0];
log.info("serviceRemoved() - Service: " + name); log.info("serviceRemoved() - Service: " + name);
NetworkService networkService = new NetworkService(name, service, serviceInfo.getSubtype(), serviceInfo.getApplication(), serviceInfo.getURLs()[0], color); Platform.runLater( () -> {
while (observableList.contains(networkService)) { observableList.stream().filter(e -> (
Platform.runLater(() -> { e.getName().equals(name) && e.getUrl().equals(url)
observableList.remove(networkService); )).forEach(observableList::remove);
}); });
} }
} }
}
@Override @Override
public void serviceResolved(ServiceEvent event) { public void serviceResolved(ServiceEvent event) {
@ -56,7 +57,15 @@ public class NetworkServiceListener implements ServiceListener {
String name = serviceInfo.getName(); String name = serviceInfo.getName();
String app = serviceInfo.getApplication(); String app = serviceInfo.getApplication();
log.info("serviceResolved() - Service: {} - {} with url {}", app, name, url); log.info("serviceResolved() - Service: {} - {} with url {}", app, name, url);
NetworkService networkService = new NetworkService(name, service, serviceInfo.getSubtype(), app, url, color); NetworkService networkService = new NetworkService(name, service, serviceInfo.getSubtype(), app, url, color);
for (Iterator<String> it = serviceInfo.getPropertyNames().asIterator(); it.hasNext(); ) {
String key = it.next();
String value = serviceInfo.getPropertyString(key);
//log.info(" -> " + key + " = " + value);
networkService.addProperty(key, value);
}
Platform.runLater(() -> { Platform.runLater(() -> {
if(!observableList.contains(networkService)) { if(!observableList.contains(networkService)) {
observableList.add(networkService); observableList.add(networkService);

View file

@ -1,20 +0,0 @@
package biz.nellemann.mdexpl.model;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.PostConstruct;
import javax.inject.Singleton;
@Singleton
public class MainModel {
private static final Logger log = LoggerFactory.getLogger(MainModel.class);
@PostConstruct
public void initialize() {
log.info("initialize()");
}
}

View file

@ -1,8 +1,11 @@
package biz.nellemann.mdexpl.model; package biz.nellemann.mdexpl.model;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.ObservableMap;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import java.util.Objects; import java.util.*;
public class NetworkService { public class NetworkService {
@ -12,6 +15,8 @@ public class NetworkService {
private String app; private String app;
private String url; private String url;
private Color color; private Color color;
private final ArrayList<String> propertiesList = new ArrayList<>();
public NetworkService(String name, String type, String subType, String app, String url, Color color) { public NetworkService(String name, String type, String subType, String app, String url, Color color) {
this.name = name; this.name = name;
@ -71,10 +76,18 @@ public class NetworkService {
this.color = color; this.color = color;
} }
public void addProperty(String key, String value) {
propertiesList.add(key + ": " + value);
}
public ArrayList<?> getProperties() {
return propertiesList;
}
@Override @Override
public String toString() { public String toString() {
return name + " (" + type + "), app=" + app + ", url=" + url; return name + " (" + app + ") " + url;
} }

View file

@ -2,14 +2,12 @@ package biz.nellemann.mdexpl.service;
import biz.nellemann.mdexpl.NetworkServiceListener; import biz.nellemann.mdexpl.NetworkServiceListener;
import biz.nellemann.mdexpl.model.NetworkService; import biz.nellemann.mdexpl.model.NetworkService;
import jakarta.inject.Singleton;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.inject.Singleton;
import javax.jmdns.JmDNS; import javax.jmdns.JmDNS;
import java.io.IOException; import java.io.IOException;
import java.net.InetAddress; import java.net.InetAddress;
@ -22,16 +20,10 @@ public class DiscoveryService {
private final static Logger log = LoggerFactory.getLogger(DiscoveryService.class); private final static Logger log = LoggerFactory.getLogger(DiscoveryService.class);
private ObservableList<NetworkService> observableList; private final ObservableList<NetworkService> observableList;
private JmDNS jmdns; private JmDNS jmdns;
/*private final HashMap<String, Color> services = HashMap<>({
"http", "https", "upnp", "ssh", "sip", "rdp", "ntp", "rdp", "rtsp",
"ntp", "smb", "nfs", "llrp", "ftp", "ep", "daap", "ipp", "ipps",
"googlecast", "sonos", "airplay", "smartenergy", "skype", "bittorrent"
);*/
// From: http://www.dns-sd.org/serviceTypes.html + // From: http://www.dns-sd.org/serviceTypes.html +
// https://jonathanmumm.com/tech-it/mdns-bonjour-bible-common-service-strings-for-various-vendors/ + some guesses // https://jonathanmumm.com/tech-it/mdns-bonjour-bible-common-service-strings-for-various-vendors/ + some guesses
private final Map<String, Color> services = new HashMap<>() {{ private final Map<String, Color> services = new HashMap<>() {{
@ -64,44 +56,29 @@ public class DiscoveryService {
put("homekit", Color.LIGHTGREEN); put("homekit", Color.LIGHTGREEN);
put("homebridge", Color.LIGHTGREEN); put("homebridge", Color.LIGHTGREEN);
put("device-info", Color.DARKGREEN);
//put("rdlink", Color.DARKGREEN);
put("sip", Color.YELLOW); put("sip", Color.YELLOW);
put("skype", Color.YELLOW); put("skype", Color.YELLOW);
//put("rdlink", Color.KHAKI); //put("rdlink", Color.KHAKI);
}}; }};
public DiscoveryService(ObservableList<NetworkService> list) {
log.info("DiscoveryService()");
this.observableList = list;
@PostConstruct
public void initialize() {
log.info("initialize()");
try { try {
jmdns = JmDNS.create(InetAddress.getByName("0.0.0.0"), "mdnsExplorer"); jmdns = JmDNS.create(InetAddress.getByName("0.0.0.0"), "mdnsExplorer");
} catch (IOException e) {
log.error("initialize() - {}", e.getMessage());
}
}
@PreDestroy
public void destroy() {
if(jmdns != null) {
try {
jmdns.close();
} catch (IOException e) {
log.error("destroy() - {}", e.getMessage());
}
}
}
public void setObservableList(ObservableList<NetworkService> list) {
this.observableList = list;
services.forEach((item, color) -> { services.forEach((item, color) -> {
String service = String.format("_%s._%s.local.", item, "tcp"); String service = String.format("_%s._%s.local.", item, "tcp");
NetworkServiceListener networkServiceListener = new NetworkServiceListener(service, observableList, color); NetworkServiceListener networkServiceListener = new NetworkServiceListener(service, observableList, color);
jmdns.addServiceListener(service, networkServiceListener); jmdns.addServiceListener(service, networkServiceListener);
}); });
} catch (IOException e) {
log.error("initialize() - {}", e.getMessage());
}
} }
} }

View file

@ -1,64 +0,0 @@
package biz.nellemann.mdexpl.view;
import com.gluonhq.charm.glisten.application.AppManager;
import com.gluonhq.charm.glisten.control.AppBar;
import com.gluonhq.charm.glisten.mvc.View;
import com.gluonhq.charm.glisten.visual.MaterialDesignIcon;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.util.ResourceBundle;
public class AboutPresenter {
private static final Logger log = LoggerFactory.getLogger(AboutPresenter.class);
@FXML
private ResourceBundle resources;
@FXML
View about;
@FXML
Label labelInfoWebsite;
@FXML
Label labelVersion;
@Inject String appVersion;
@Inject String aboutWebsite;
@FXML
public void initialize() {
log.info("initialize()");
about.showingProperty().addListener((obs, oldValue, newValue) -> {
if (newValue) {
AppManager appManager = AppManager.getInstance();
AppBar appBar = appManager.getAppBar();
appBar.setNavIcon(MaterialDesignIcon.MENU.button(e ->
appManager.getDrawer().open()));
appBar.setTitleText("About");
appBar.getActionItems().add(MaterialDesignIcon.CLOSE.button(e -> {
appManager.goHome();
}));
}
});
labelVersion.setText(appVersion);
labelInfoWebsite.setText(aboutWebsite);
}
}

View file

@ -1,52 +0,0 @@
package biz.nellemann.mdexpl.view;
import biz.nellemann.mdexpl.App;
import com.gluonhq.charm.glisten.afterburner.AppView;
import com.gluonhq.charm.glisten.afterburner.AppViewRegistry;
import com.gluonhq.charm.glisten.afterburner.Utils;
import com.gluonhq.charm.glisten.application.AppManager;
import com.gluonhq.charm.glisten.control.Avatar;
import com.gluonhq.charm.glisten.control.NavigationDrawer;
import com.gluonhq.charm.glisten.visual.MaterialDesignIcon;
import javafx.scene.image.Image;
import java.util.Locale;
import java.util.Objects;
import static com.gluonhq.charm.glisten.afterburner.AppView.Flag.*;
public class AppViewManager {
public static final AppViewRegistry REGISTRY = new AppViewRegistry();
// Shown in drawer
public static final AppView PRIMARY_VIEW = view("Explorer", MainPresenter.class, MaterialDesignIcon.HOME, SHOW_IN_DRAWER, HOME_VIEW, SKIP_VIEW_STACK);
public static final AppView ABOUT_VIEW = view("About", AboutPresenter.class, MaterialDesignIcon.HELP, SHOW_IN_DRAWER);
private static AppView view(String title, Class<?> presenterClass, MaterialDesignIcon menuIcon, AppView.Flag... flags ) {
return REGISTRY.createView(name(presenterClass), title, presenterClass, menuIcon, flags);
}
private static String name(Class<?> presenterClass) {
return presenterClass.getSimpleName().toUpperCase(Locale.ROOT).replace("PRESENTER", "");
}
public static void registerViewsAndDrawer() {
for (AppView view : REGISTRY.getViews()) {
view.registerView();
}
NavigationDrawer.Header header = new NavigationDrawer.Header("mDNS Explorer",
"Multicast DNS Explorer",
new Avatar(48, new Image(Objects.requireNonNull(App.class.getResourceAsStream("/icon.png"))))
);
Utils.buildDrawer(AppManager.getInstance().getDrawer(), header, REGISTRY.getViews());
//NavigationDrawer.Footer footer = new NavigationDrawer.Footer("Bla");
//AppManager.getInstance().getDrawer().setFooter(footer);
}
}

View file

@ -1,77 +0,0 @@
package biz.nellemann.mdexpl.view;
import biz.nellemann.mdexpl.NetworkServiceCell;
import biz.nellemann.mdexpl.model.NetworkService;
import biz.nellemann.mdexpl.model.MainModel;
import biz.nellemann.mdexpl.service.DiscoveryService;
import com.gluonhq.charm.glisten.application.AppManager;
import com.gluonhq.charm.glisten.control.AppBar;
import com.gluonhq.charm.glisten.control.CharmListView;
import com.gluonhq.charm.glisten.control.LifecycleEvent;
import com.gluonhq.charm.glisten.mvc.View;
import com.gluonhq.charm.glisten.visual.MaterialDesignIcon;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TouchEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.util.ResourceBundle;
public class MainPresenter {
private static final Logger log = LoggerFactory.getLogger(MainPresenter.class);
@FXML
private View main;
@Inject
private MainModel model;
@Inject
private DiscoveryService discoveryService;
@FXML
private ResourceBundle resources;
@FXML
private CharmListView<NetworkService, Integer> charmListView;
private final ObservableList<NetworkService> devicesList = FXCollections.observableArrayList();
@FXML
public void initialize() {
log.info("initialize()");
main.showingProperty().addListener((obs, oldValue, newValue) -> {
if (newValue) {
AppManager appManager = AppManager.getInstance();
AppBar appBar = appManager.getAppBar();
appBar.setNavIcon(MaterialDesignIcon.MENU.button(e ->
appManager.getDrawer().open()));
appBar.setTitleText("mDNS Explorer");
}
});
discoveryService.setObservableList(devicesList);
charmListView.setItems(devicesList);
charmListView.setCellFactory(p -> new NetworkServiceCell());
}
public void onEventShowing(LifecycleEvent lifecycleEvent) {
}
public void onEventHiding(LifecycleEvent lifecycleEvent) {
}
}

View file

@ -0,0 +1,12 @@
module biz.nellemann.mdexpl {
requires javafx.controls;
requires javafx.fxml;
requires javax.jmdns;
requires org.slf4j;
requires jakarta.inject;
requires jakarta.annotation;
requires java.desktop;
opens biz.nellemann.mdexpl to javafx.fxml;
exports biz.nellemann.mdexpl;
}

View file

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.layout.BorderPane?>
<BorderPane fx:id="view" prefHeight="600.0" prefWidth="800.0" xmlns="http://javafx.com/javafx/20.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="biz.nellemann.mdexpl.MainPresenter">
<center>
<ListView fx:id="listView" BorderPane.alignment="CENTER" />
</center>
<right>
<ListView fx:id="propertiesList" />
</right>
</BorderPane>

View file

@ -1,45 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import com.gluonhq.charm.glisten.control.Icon?>
<?import com.gluonhq.charm.glisten.mvc.View?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<View fx:id="about" xmlns="http://javafx.com/javafx/20.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="biz.nellemann.mdexpl.view.AboutPresenter">
<VBox alignment="CENTER">
<Label alignment="CENTER" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" minHeight="-Infinity" minWidth="-Infinity" text="mDNS Explorer">
<VBox.margin>
<Insets bottom="5.0" />
</VBox.margin>
<font>
<Font size="36.0" />
</font>
</Label>
<Label fx:id="labelVersion" alignment="CENTER" layoutX="10.0" layoutY="234.0" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" text="version" textAlignment="CENTER">
<VBox.margin>
<Insets bottom="5.0" top="5.0" />
</VBox.margin>
</Label>
<HBox alignment="CENTER" spacing="5.0">
<children>
<Icon content="INFO" />
<Label fx:id="labelInfoWebsite" alignment="CENTER" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" textAlignment="CENTER">
</Label>
</children>
<VBox.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</VBox.margin>
</HBox>
</VBox>
<padding>
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0" />
</padding>
</View>

View file

@ -1,8 +0,0 @@
###
# properties defined in configuration.properties per folder (component)
# can be directly injected into a presenter
appCopyright=@copyright@
appVersion=@version@
aboutWebsite=https://www.nellemann.biz

View file

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import com.gluonhq.charm.glisten.control.BottomNavigation?>
<?import com.gluonhq.charm.glisten.control.BottomNavigationButton?>
<?import com.gluonhq.charm.glisten.control.CharmListView?>
<?import com.gluonhq.charm.glisten.control.Icon?>
<?import com.gluonhq.charm.glisten.mvc.View?>
<?import javafx.scene.layout.BorderPane?>
<View fx:id="main" onHiding="#onEventHiding" onShowing="#onEventShowing" xmlns="http://javafx.com/javafx/20.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="biz.nellemann.mdexpl.view.MainPresenter">
<center>
<CharmListView fx:id="charmListView" BorderPane.alignment="CENTER" />
</center>
</View>