initial AI slop commit. to be refactored!

This commit is contained in:
2026-05-16 22:56:08 +02:00
commit e905891b11
21 changed files with 1758 additions and 0 deletions
+40
View File
@@ -0,0 +1,40 @@
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
.kotlin
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store
/.idea/
Binary file not shown.
+34
View File
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.rdesk</groupId>
<artifactId>rdesk</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- Source: https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.14.0</version>
<scope>compile</scope>
</dependency>
<!-- Source: https://mvnrepository.com/artifact/com.formdev/flatlaf -->
<dependency>
<groupId>com.formdev</groupId>
<artifactId>flatlaf</artifactId>
<version>3.7.1</version>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>
+88
View File
@@ -0,0 +1,88 @@
package com.rdesk;
import com.rdesk.applications.system.Exit;
import com.rdesk.applications.productivity.Notes;
import com.rdesk.applications.system.TaskManager;
import com.rdesk.applications.system.filehaj.FileExplorerApp;
import com.rdesk.kernel.*;
import javax.swing.*;
public class Main {
private static final boolean RUN_IN_FS = true;
private static final boolean USE_SYSDEFAULT_LAF = false;
private static final boolean USE_FLATLAF = false;
public static void main(String[] args) {
SwingUtilities.invokeLater(Main::boot);
}
private static void boot() {
Kernel kernel = Kernel.get();
kernel.boot();
JFrame frame = new JFrame("Java OS");
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.addWindowListener(new java.awt.event.WindowAdapter() {
@Override
public void windowClosing(java.awt.event.WindowEvent e) {
kernel.shutdown();
}
});
if (USE_FLATLAF) {
try {
UIManager.setLookAndFeel(
"com.formdev.flatlaf.themes.FlatMacLightLaf"
);
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
JOptionPane.showMessageDialog(
null,
ex.getMessage() + "@USE_FLATLAF"
);
}
}
if (USE_SYSDEFAULT_LAF) {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
JOptionPane.showMessageDialog(
null,
ex.getMessage() + "@USE_SYSDEFAULT_LAF"
);
}
}
if (RUN_IN_FS) {
frame.setUndecorated(true);
frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
} else {
frame.setSize(1200, 800);
frame.setLocationRelativeTo(null);
}
JDesktopPane desktop = new JDesktopPane();
kernel.windows().attachDesktop(desktop);
AppLauncher launcher = new AppLauncher(kernel);
registerSystemApps(launcher);
JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, launcher, desktop);
split.setDividerSize(0);
split.setEnabled(false);
frame.setContentPane(split);
frame.setVisible(true);
}
private static void registerSystemApps(AppLauncher launcher) {
launcher.addApp(new Notes(), "Productivity");
launcher.addApp(new Exit(), "System");
launcher.addApp(new TaskManager(), "System");
launcher.addApp(new FileExplorerApp(), "System");
}
}
@@ -0,0 +1,37 @@
package com.rdesk.applications.productivity;
import com.rdesk.kernel.*;
import javax.swing.*;
public class Notes implements DesktopApp {
@Override
public String getId() {
return "notes";
}
@Override
public String getName() {
return "Notes";
}
@Override
public JInternalFrame createWindow(Kernel kernel) {
JInternalFrame frame = new JInternalFrame(
"Notes",
true, // resizable
true, // closable
true, // maximizable
true // iconifiable
);
frame.setSize(400, 300);
frame.setLayout(new java.awt.BorderLayout());
JTextArea textArea = new JTextArea();
frame.add(new JScrollPane(textArea), java.awt.BorderLayout.CENTER);
frame.setVisible(true);
return frame;
}
}
@@ -0,0 +1,66 @@
package com.rdesk.applications.system;
import com.rdesk.kernel.DesktopApp;
import com.rdesk.kernel.Kernel;
import javax.swing.*;
import java.awt.*;
public class Exit implements DesktopApp {
@Override
public String getId() {
return "exit";
}
@Override
public String getName() {
return "System Exit";
}
@Override
public JInternalFrame createWindow(Kernel kernel) {
JInternalFrame frame = new JInternalFrame("System Controls", true, true);
frame.setSize(350, 180);
frame.setResizable(false);
JPanel mainPanel = new JPanel(new GridBagLayout());
mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.insets = new Insets(5, 5, 5, 5);
JLabel label = new JLabel("Choose an action:");
label.setFont(label.getFont().deriveFont(Font.BOLD));
mainPanel.add(label, gbc);
JButton shutdownButton = new JButton("Shutdown OS");
shutdownButton.setFocusPainted(false);
shutdownButton.addActionListener(e -> confirmAndShutdown(kernel, frame));
mainPanel.add(shutdownButton, gbc);
JButton cancelButton = new JButton("Cancel");
cancelButton.setFocusPainted(false);
cancelButton.addActionListener(e -> frame.dispose());
mainPanel.add(cancelButton, gbc);
frame.add(mainPanel);
frame.setDefaultCloseOperation(JInternalFrame.DISPOSE_ON_CLOSE);
return frame;
}
private void confirmAndShutdown(Kernel kernel, JInternalFrame frame) {
int choice = JOptionPane.showConfirmDialog(
frame,
"Are you sure you want to shut down the operating system?\nAll unsaved work will be lost.",
"Confirm Shutdown",
JOptionPane.YES_NO_OPTION,
JOptionPane.WARNING_MESSAGE
);
if (choice == JOptionPane.YES_OPTION) {
frame.dispose(); // close the confirmation window first
kernel.shutdown();
}
}
}
@@ -0,0 +1,342 @@
package com.rdesk.applications.system;
import com.rdesk.kernel.DesktopApp;
import com.rdesk.kernel.Kernel;
import javax.swing.*;
import javax.swing.Timer;
import javax.swing.event.InternalFrameAdapter;
import javax.swing.event.InternalFrameEvent;
import javax.swing.table.AbstractTableModel;
import java.awt.*;
import java.awt.geom.Path2D;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;
import java.lang.management.OperatingSystemMXBean;
import java.util.*;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class TaskManager implements DesktopApp {
private ScheduledExecutorService scheduler;
@Override
public String getId() {
return "taskmanager";
}
@Override
public String getName() {
return "Task Manager";
}
@Override
public JInternalFrame createWindow(Kernel kernel) {
JInternalFrame frame = new JInternalFrame("Task Manager", true, true, true, true);
frame.setSize(750, 550);
frame.setMinimumSize(new Dimension(600, 400));
frame.setResizable(true);
JTabbedPane tabbedPane = new JTabbedPane();
// ---- Processes Tab ----
JPanel processesPanel = createProcessesPanel(kernel);
tabbedPane.addTab("Processes", processesPanel);
// ---- Performance Tab ----
JPanel performancePanel = createPerformancePanel();
tabbedPane.addTab("Performance", performancePanel);
frame.add(tabbedPane, BorderLayout.CENTER);
// Start background updater for the graphs
scheduler = Executors.newSingleThreadScheduledExecutor();
Runnable updateTask = () -> {
if (!frame.isVisible()) return;
SwingUtilities.invokeLater(() -> {
// Update performance graphs
Component[] comps = performancePanel.getComponents();
for (Component c : comps) {
if (c instanceof GraphPanel) {
((GraphPanel) c).addDataPoint();
((GraphPanel) c).repaint();
}
}
});
};
scheduler.scheduleAtFixedRate(updateTask, 0, 1, TimeUnit.SECONDS);
frame.addInternalFrameListener(new InternalFrameAdapter() {
@Override
public void internalFrameClosed(InternalFrameEvent e) {
if (scheduler != null && !scheduler.isShutdown()) {
scheduler.shutdown();
}
}
});
frame.setDefaultCloseOperation(JInternalFrame.DISPOSE_ON_CLOSE);
return frame;
}
// ------------------------------------------------------------------
// Processes Tab
// ------------------------------------------------------------------
private JPanel createProcessesPanel(Kernel kernel) {
JPanel panel = new JPanel(new BorderLayout());
ProcessTableModel model = new ProcessTableModel(kernel);
JTable table = new JTable(model);
table.setFillsViewportHeight(true);
table.getColumnModel().getColumn(0).setPreferredWidth(200);
table.getColumnModel().getColumn(1).setPreferredWidth(100);
table.getColumnModel().getColumn(2).setPreferredWidth(80);
table.setRowHeight(22);
JScrollPane scrollPane = new JScrollPane(table);
panel.add(scrollPane, BorderLayout.CENTER);
JPanel toolbar = new JPanel(new FlowLayout(FlowLayout.RIGHT));
JButton endTaskButton = new JButton("End Task");
endTaskButton.addActionListener(e -> {
int selected = table.getSelectedRow();
if (selected >= 0) {
String appId = model.getAppIdAt(selected);
confirmAndKill(kernel, appId, model);
} else {
JOptionPane.showMessageDialog(panel, "Select a task to end.", "No Selection", JOptionPane.INFORMATION_MESSAGE);
}
});
JButton refreshButton = new JButton("Refresh");
refreshButton.addActionListener(e -> model.refresh());
toolbar.add(endTaskButton);
toolbar.add(refreshButton);
panel.add(toolbar, BorderLayout.SOUTH);
Timer timer = new Timer(2000, e -> model.refresh());
timer.start();
panel.putClientProperty("timer", timer);
return panel;
}
private void confirmAndKill(Kernel kernel, String appId, ProcessTableModel model) {
/*
int confirm = JOptionPane.showConfirmDialog(null,
"End task '" + appId + "'?\nAll unsaved work will be lost.",
"Confirm End Task",
JOptionPane.YES_NO_OPTION,
JOptionPane.WARNING_MESSAGE);
if (confirm == JOptionPane.YES_OPTION) {
*/
kernel.windows().closeAllByAppId(appId);
model.refresh();
}
private JPanel createPerformancePanel() {
JPanel panel = new JPanel(new GridLayout(2, 1, 10, 10));
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
long maxMemoryMB = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getMax() / (1024 * 1024);
GraphPanel cpuGraph = new GraphPanel("CPU Usage", "%", Color.BLUE, 0, 100);
GraphPanel memoryGraph = new GraphPanel("Memory Usage", "MB", Color.GREEN, 0, (int) maxMemoryMB);
panel.add(cpuGraph);
panel.add(memoryGraph);
return panel;
}
private static class GraphPanel extends JPanel {
private final String title;
private final String unit;
private final Color lineColor;
private final int maxY;
private final LinkedList<Integer> data = new LinkedList<>();
private final int maxDataPoints = 60; // 60 points = 1 minute at 1 update/sec
public GraphPanel(String title, String unit, Color lineColor, int minY, int maxY) {
this.title = title;
this.unit = unit;
this.lineColor = lineColor;
this.maxY = maxY;
setBackground(Color.WHITE);
setBorder(BorderFactory.createTitledBorder(title));
// Initialize with zeros
for (int i = 0; i < maxDataPoints; i++) data.add(0);
setPreferredSize(new Dimension(400, 200));
}
public void addDataPoint() {
int value = 0;
if (title.contains("CPU")) {
OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean();
double load = osBean.getSystemLoadAverage();
if (load > 0) {
int processors = osBean.getAvailableProcessors();
double cpuPercent = (load / processors) * 100;
value = (int) Math.min(Math.max(cpuPercent, 0), maxY);
}
} else if (title.contains("Memory")) {
MemoryUsage heap = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
long usedMB = heap.getUsed() / (1024 * 1024);
value = (int) Math.min(usedMB, maxY);
}
synchronized (data) {
data.addLast(value);
if (data.size() > maxDataPoints) data.removeFirst();
}
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
int w = getWidth();
int h = getHeight();
int leftMargin = 50;
int rightMargin = 20;
int topMargin = 40; // increased for title space
int bottomMargin = 30;
int graphWidth = w - leftMargin - rightMargin;
int graphHeight = h - topMargin - bottomMargin;
if (graphWidth <= 0 || graphHeight <= 0) return;
// Clear background
g2.setColor(Color.WHITE);
g2.fillRect(0, 0, w, h);
// Draw axes
g2.setColor(Color.BLACK);
g2.drawLine(leftMargin, topMargin, leftMargin, h - bottomMargin);
g2.drawLine(leftMargin, h - bottomMargin, w - rightMargin, h - bottomMargin);
// Draw grid lines (horizontal)
g2.setColor(Color.LIGHT_GRAY);
for (int i = 0; i <= 4; i++) {
int y = topMargin + (graphHeight * i / 4);
g2.drawLine(leftMargin, y, w - rightMargin, y);
}
// Draw Y-axis labels
g2.setColor(Color.BLACK);
FontMetrics fm = g2.getFontMetrics();
for (int i = 0; i <= 4; i++) {
int value = maxY - (maxY * i / 4);
String label = String.valueOf(value);
int labelWidth = fm.stringWidth(label);
int y = topMargin + (graphHeight * i / 4) + fm.getHeight() / 2 - 2;
g2.drawString(label, leftMargin - labelWidth - 5, y);
}
// Draw unit on Y axis
g2.drawString(unit, leftMargin - 25, topMargin - 5);
// Plot data
if (data.isEmpty()) return;
synchronized (data) {
Path2D path = new Path2D.Double();
double xStep = (double) graphWidth / (data.size() - 1);
boolean first = true;
for (int i = 0; i < data.size(); i++) {
double x = leftMargin + i * xStep;
double value = data.get(i);
double y = h - bottomMargin - (value * graphHeight / maxY);
if (first) {
path.moveTo(x, y);
first = false;
} else {
path.lineTo(x, y);
}
}
g2.setColor(lineColor);
g2.setStroke(new BasicStroke(2f));
g2.draw(path);
}
// Draw current value in top-right corner
int currentValue = data.isEmpty() ? 0 : data.getLast();
String valueStr = String.format("Current: %d %s", currentValue, unit);
g2.setColor(Color.BLACK);
Font smallFont = g2.getFont().deriveFont(Font.PLAIN, 11f);
g2.setFont(smallFont);
int valueWidth = fm.stringWidth(valueStr);
g2.drawString(valueStr, w - rightMargin - valueWidth - 5, topMargin - 10);
}
}
private static class ProcessTableModel extends AbstractTableModel {
private final Kernel kernel;
private List<ProcessInfo> processes = new ArrayList<>();
private final String[] columns = {"Application", "Memory (MB)", "Windows"};
ProcessTableModel(Kernel kernel) {
this.kernel = kernel;
refresh();
}
void refresh() {
processes.clear();
Map<String, Integer> windowCounts = new LinkedHashMap<>();
for (JInternalFrame frame : kernel.windows().getAllWindows()) {
Object prop = frame.getClientProperty("appId");
if (prop instanceof String) {
String appId = (String) prop;
windowCounts.put(appId, windowCounts.getOrDefault(appId, 0) + 1);
}
}
for (Map.Entry<String, Integer> entry : windowCounts.entrySet()) {
String appId = entry.getKey();
DesktopApp app = kernel.apps().getApp(appId);
String appName = (app != null) ? app.getName() : appId;
long memoryMB = entry.getValue() * 8L; // rough estimation
processes.add(new ProcessInfo(appName, appId, entry.getValue(), memoryMB));
}
fireTableDataChanged();
}
String getAppIdAt(int row) {
return processes.get(row).id;
}
@Override
public int getRowCount() { return processes.size(); }
@Override
public int getColumnCount() { return columns.length; }
@Override
public String getColumnName(int col) { return columns[col]; }
@Override
public Object getValueAt(int row, int col) {
ProcessInfo info = processes.get(row);
switch (col) {
case 0: return info.name;
case 1: return info.memoryMB;
case 2: return info.windowCount;
default: return "";
}
}
private static class ProcessInfo {
String name, id;
int windowCount;
long memoryMB;
ProcessInfo(String name, String id, int windowCount, long memoryMB) {
this.name = name; this.id = id; this.windowCount = windowCount; this.memoryMB = memoryMB;
}
}
}
}
@@ -0,0 +1,110 @@
// FileExplorerApp.java - fix border and import
package com.rdesk.applications.system.filehaj;
import com.rdesk.kernel.DesktopApp;
import com.rdesk.kernel.Kernel;
import javax.swing.*;
import java.awt.*;
import java.io.File;
import java.util.List;
public class FileExplorerApp implements DesktopApp {
private JInternalFrame frame;
private File currentDirectory;
private final java.util.Stack<File> backStack = new java.util.Stack<>();
private final java.util.Stack<File> forwardStack = new java.util.Stack<>();
private NavigationPanel navPanel;
private FileTreePanel treePanel;
private FileListPanel listPanel;
private JLabel statusBar;
@Override
public String getId() { return "filehaj"; }
@Override
public String getName() { return "FileHaj"; }
@Override
public JInternalFrame createWindow(Kernel kernel) {
frame = new JInternalFrame("FileHaj", true, true, true, true);
frame.setSize(750, 450);
frame.setMinimumSize(new Dimension(550, 300));
currentDirectory = new File(System.getProperty("user.home"));
navPanel = new NavigationPanel(
this::goBack, this::goForward, this::goUp,
this::refreshAll, this::navigateTo, this::refreshAll
);
treePanel = new FileTreePanel(this::navigateTo);
listPanel = new FileListPanel(f -> {
if (f.isDirectory()) navigateTo(f);
else openFile(f);
});
statusBar = new JLabel(" ");
statusBar.setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 5));
frame.add(navPanel, BorderLayout.NORTH);
JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, treePanel, listPanel);
split.setDividerLocation(200);
frame.add(split, BorderLayout.CENTER);
frame.add(statusBar, BorderLayout.SOUTH);
refreshAll();
frame.setDefaultCloseOperation(JInternalFrame.DISPOSE_ON_CLOSE);
return frame;
}
private void navigateTo(File dir) {
if (dir == null || !dir.exists() || !dir.isDirectory() || !dir.canRead()) return;
if (!dir.equals(currentDirectory)) {
backStack.push(currentDirectory);
forwardStack.clear();
}
currentDirectory = dir;
refreshAll();
}
private void goBack() {
if (!backStack.isEmpty()) {
forwardStack.push(currentDirectory);
currentDirectory = backStack.pop();
refreshAll();
}
}
private void goForward() {
if (!forwardStack.isEmpty()) {
backStack.push(currentDirectory);
currentDirectory = forwardStack.pop();
refreshAll();
}
}
private void goUp() {
File parent = currentDirectory.getParentFile();
if (parent != null && parent.canRead()) navigateTo(parent);
}
private void refreshAll() {
navPanel.setAddress(currentDirectory.getAbsolutePath());
navPanel.updateNavButtons(!backStack.isEmpty(), !forwardStack.isEmpty());
statusBar.setText("Loading...");
FileUtils.runAsync(() -> FileUtils.filterAndSort(currentDirectory.listFiles()), items -> {
listPanel.setFiles(items);
statusBar.setText(currentDirectory.getAbsolutePath() + "" + items.size() + " items");
treePanel.selectFile(currentDirectory);
});
}
private void openFile(File file) {
try {
Desktop.getDesktop().open(file);
} catch (Exception ex) {
JOptionPane.showMessageDialog(frame, "Cannot open file: " + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
}
}
}
@@ -0,0 +1,52 @@
package com.rdesk.applications.system.filehaj;
import javax.swing.*;
import java.awt.*;
import java.io.File;
import java.util.List;
public class FileListPanel extends JPanel {
private DefaultListModel<File> listModel;
private JList<File> fileList;
public FileListPanel(java.util.function.Consumer<File> onDoubleClick) {
setLayout(new BorderLayout());
listModel = new DefaultListModel<>();
fileList = new JList<>(listModel);
fileList.setCellRenderer(new FileListCellRenderer());
fileList.addMouseListener(new java.awt.event.MouseAdapter() {
@Override
public void mouseClicked(java.awt.event.MouseEvent e) {
if (e.getClickCount() == 2) {
File f = fileList.getSelectedValue();
if (f != null) onDoubleClick.accept(f);
}
}
});
add(new JScrollPane(fileList), BorderLayout.CENTER);
}
public void setFiles(List<File> files) {
SwingUtilities.invokeLater(() -> {
listModel.clear();
files.forEach(listModel::addElement);
});
}
private static class FileListCellRenderer extends DefaultListCellRenderer {
@Override
public Component getListCellRendererComponent(JList<?> list, Object value,
int index, boolean isSelected, boolean cellHasFocus) {
super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
if (value instanceof File) {
File f = (File) value;
setText(f.getName());
setIcon(UIManager.getIcon(f.isDirectory() ? "FileView.directoryIcon" : "FileView.fileIcon"));
if (f.isFile()) {
setText(f.getName() + " (" + FileUtils.formatFileSize(f.length()) + ")");
}
}
return this;
}
}
}
@@ -0,0 +1,167 @@
package com.rdesk.applications.system.filehaj;
import javax.swing.*;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeWillExpandListener;
import javax.swing.tree.*;
import java.awt.*;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class FileTreePanel extends JPanel {
private JTree tree;
private DefaultTreeModel treeModel;
private DefaultMutableTreeNode rootNode;
public FileTreePanel(java.util.function.Consumer<File> onNavigate) {
setLayout(new BorderLayout());
rootNode = new DefaultMutableTreeNode("Computer");
treeModel = new DefaultTreeModel(rootNode);
tree = new JTree(treeModel);
tree.setCellRenderer(new MinimalTreeCellRenderer());
tree.setShowsRootHandles(true);
tree.addTreeSelectionListener(e -> {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
if (node != null && node.getUserObject() instanceof File) {
onNavigate.accept((File) node.getUserObject());
}
});
tree.addTreeWillExpandListener(new TreeWillExpandListener() {
@Override public void treeWillExpand(TreeExpansionEvent event) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) event.getPath().getLastPathComponent();
if (node.getChildCount() == 1 && node.getFirstChild() instanceof PlaceholderNode) {
loadTreeChildren(node);
}
}
@Override public void treeWillCollapse(TreeExpansionEvent event) {}
});
add(new JScrollPane(tree), BorderLayout.CENTER);
// Build initial tree
buildInitialTree();
}
private void buildInitialTree() {
String home = System.getProperty("user.home");
addNode(new NamedFile(new File(home), "Home"), true);
addNode(new NamedFile(new File(home, "Desktop"), "Desktop"), true);
addNode(new NamedFile(new File(home, "Documents"), "Documents"), true);
addNode(new NamedFile(new File(home, "Downloads"), "Downloads"), true);
addNode(new NamedFile(new File(home, "Music"), "Music"), true);
addNode(new NamedFile(new File(home, "Pictures"), "Pictures"), true);
addNode(new NamedFile(new File(home, "Videos"), "Videos"), true);
for (File root : File.listRoots()) {
if (root.getAbsolutePath().startsWith("C:")) {
addNode(root, true);
break;
}
}
// Other drives asynchronously
FileUtils.runAsync(() -> {
List<DefaultMutableTreeNode> others = new ArrayList<>();
for (File root : File.listRoots()) {
if (!root.getAbsolutePath().startsWith("C:") && root.exists() && root.canRead()) {
DefaultMutableTreeNode node = new DefaultMutableTreeNode(root);
node.add(new PlaceholderNode());
others.add(node);
}
}
SwingUtilities.invokeLater(() -> {
for (DefaultMutableTreeNode node : others) {
rootNode.add(node);
}
treeModel.nodeStructureChanged(rootNode);
});
}, () -> tree.expandRow(0));
}
private void addNode(Object userObject, boolean withPlaceholder) {
DefaultMutableTreeNode node = new DefaultMutableTreeNode(userObject);
if (withPlaceholder) node.add(new PlaceholderNode());
rootNode.add(node);
treeModel.nodeStructureChanged(rootNode);
}
private void loadTreeChildren(DefaultMutableTreeNode parent) {
File dir = (File) parent.getUserObject();
parent.removeAllChildren();
parent.add(new PlaceholderNode());
treeModel.nodeStructureChanged(parent);
FileUtils.runAsync(() -> {
File[] subdirs = dir.listFiles(f -> f.isDirectory() && FileUtils.shouldShow(f));
List<DefaultMutableTreeNode> children = new ArrayList<>();
if (subdirs != null) {
Arrays.sort(subdirs);
for (File child : subdirs) {
DefaultMutableTreeNode node = new DefaultMutableTreeNode(child);
if (hasVisibleSubdirs(child)) node.add(new PlaceholderNode());
children.add(node);
}
}
SwingUtilities.invokeLater(() -> {
parent.removeAllChildren();
children.forEach(parent::add);
treeModel.nodeStructureChanged(parent);
});
}, () -> {});
}
private boolean hasVisibleSubdirs(File dir) {
File[] sub = dir.listFiles(f -> f.isDirectory() && FileUtils.shouldShow(f));
return sub != null && sub.length > 0;
}
public void selectFile(File target) {
FileUtils.runAsync(() -> findNode(rootNode, target), node -> {
if (node != null) {
TreePath path = new TreePath(node.getPath());
tree.setSelectionPath(path);
tree.scrollPathToVisible(path);
}
});
}
private DefaultMutableTreeNode findNode(DefaultMutableTreeNode node, File target) {
Object obj = node.getUserObject();
File file = (obj instanceof File) ? (File) obj : null;
if (file != null && file.equals(target)) return node;
for (int i = 0; i < node.getChildCount(); i++) {
DefaultMutableTreeNode found = findNode((DefaultMutableTreeNode) node.getChildAt(i), target);
if (found != null) return found;
}
return null;
}
private static class PlaceholderNode extends DefaultMutableTreeNode {
PlaceholderNode() { super("..."); }
}
private static class MinimalTreeCellRenderer extends DefaultTreeCellRenderer {
@Override
public Component getTreeCellRendererComponent(JTree tree, Object value,
boolean sel, boolean expanded,
boolean leaf, int row, boolean hasFocus) {
super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
if (value instanceof DefaultMutableTreeNode) {
Object obj = ((DefaultMutableTreeNode) value).getUserObject();
if (obj instanceof File) {
File f = (File) obj;
setText(f.getName().isEmpty() ? f.getPath() : f.getName());
setIcon(UIManager.getIcon(f.isDirectory() ? "FileView.directoryIcon" : "FileView.fileIcon"));
} else if (obj instanceof NamedFile) {
setText(obj.toString());
setIcon(UIManager.getIcon("FileView.directoryIcon"));
} else if (obj instanceof String && "...".equals(obj)) {
setText("Loading...");
setIcon(null);
}
}
return this;
}
}
}
@@ -0,0 +1,58 @@
package com.rdesk.applications.system.filehaj;
import javax.swing.*;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class FileUtils {
private static boolean showHidden = false;
public static boolean isShowHidden() { return showHidden; }
public static void setShowHidden(boolean show) { showHidden = show; }
public static boolean shouldShow(File f) {
if (showHidden) return true;
return !f.isHidden() && !f.getName().startsWith(".");
}
public static List<File> filterAndSort(File[] files) {
List<File> result = new ArrayList<>();
if (files == null) return result;
for (File f : files) {
if (shouldShow(f)) result.add(f);
}
result.sort((a, b) -> {
if (a.isDirectory() && !b.isDirectory()) return -1;
if (!a.isDirectory() && b.isDirectory()) return 1;
return a.getName().compareToIgnoreCase(b.getName());
});
return result;
}
public static String formatFileSize(long size) {
if (size < 1024) return size + " B";
int exp = (int) (Math.log(size) / Math.log(1024));
char pre = "KMGTPE".charAt(exp - 1);
return String.format("%.1f %sB", size / Math.pow(1024, exp), pre);
}
public static void runAsync(Runnable background, Runnable uiUpdate) {
new SwingWorker<Void, Void>() {
@Override protected Void doInBackground() { background.run(); return null; }
@Override protected void done() { uiUpdate.run(); }
}.execute();
}
// Generic async with result
public static <T> void runAsync(Supplier<T> background, Consumer<T> uiUpdate) {
new SwingWorker<T, Void>() {
@Override protected T doInBackground() { return background.get(); }
@Override protected void done() {
try { uiUpdate.accept(get()); } catch (Exception ignored) {}
}
}.execute();
}
}
@@ -0,0 +1,17 @@
package com.rdesk.applications.system.filehaj;
import java.io.File;
public class NamedFile extends File {
private final String displayName;
public NamedFile(File file, String displayName) {
super(file.getAbsolutePath());
this.displayName = displayName;
}
@Override
public String toString() {
return displayName;
}
}
@@ -0,0 +1,63 @@
package com.rdesk.applications.system.filehaj;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.awt.event.ActionListener;
import java.io.File;
public class NavigationPanel extends JPanel {
private JButton backButton, forwardButton, upButton, refreshButton;
private JTextField addressBar;
private JCheckBox hiddenToggle;
public NavigationPanel(Runnable onBack, Runnable onForward, Runnable onUp,
Runnable onRefresh, java.util.function.Consumer<File> onNavigate,
Runnable onToggleHidden) {
setLayout(new BorderLayout(5, 5));
setBorder(new EmptyBorder(5, 5, 5, 5));
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 2, 0));
backButton = new JButton("<");
forwardButton = new JButton(">");
upButton = new JButton("^");
refreshButton = new JButton("R");
backButton.setToolTipText("Back");
forwardButton.setToolTipText("Forward");
upButton.setToolTipText("Up");
refreshButton.setToolTipText("Refresh");
backButton.addActionListener(e -> onBack.run());
forwardButton.addActionListener(e -> onForward.run());
upButton.addActionListener(e -> onUp.run());
refreshButton.addActionListener(e -> onRefresh.run());
buttonPanel.add(backButton);
buttonPanel.add(forwardButton);
buttonPanel.add(upButton);
buttonPanel.add(refreshButton);
hiddenToggle = new JCheckBox("Show hidden items");
hiddenToggle.setToolTipText("Show hidden files and folders");
hiddenToggle.addActionListener(e -> {
FileUtils.setShowHidden(hiddenToggle.isSelected());
onToggleHidden.run();
});
hiddenToggle.setSelected(FileUtils.isShowHidden());
buttonPanel.add(hiddenToggle);
addressBar = new JTextField();
addressBar.addActionListener(e -> onNavigate.accept(new File(addressBar.getText())));
add(buttonPanel, BorderLayout.WEST);
add(addressBar, BorderLayout.CENTER);
}
public void setAddress(String path) { addressBar.setText(path); }
public void updateNavButtons(boolean backEnabled, boolean forwardEnabled) {
backButton.setEnabled(backEnabled);
forwardButton.setEnabled(forwardEnabled);
}
public void updateHiddenToggle() { hiddenToggle.setSelected(FileUtils.isShowHidden()); }
}
@@ -0,0 +1,221 @@
package com.rdesk.kernel;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* A clean, functional sidebar that lists applications by category.
* Categories tile vertically, each taking full width.
*/
public class AppLauncher extends JPanel {
private static final String DEFAULT_CATEGORY = "General";
private static final int EXPANDED_WIDTH = 220;
private static final int COLLAPSED_WIDTH = 40;
private final Kernel kernel;
private final Map<String, CategoryPanel> categories = new LinkedHashMap<>();
private final JPanel mainPanel;
private final JButton globalToggleButton;
private final JScrollPane scrollPane;
private boolean expanded = true;
public AppLauncher(Kernel kernel) {
this.kernel = kernel;
setLayout(new BorderLayout());
setPreferredSize(new Dimension(EXPANDED_WIDTH, 0));
globalToggleButton = new JButton("<<");
globalToggleButton.setToolTipText("Collapse sidebar");
globalToggleButton.setFocusPainted(false);
globalToggleButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
globalToggleButton.addActionListener(this::toggleSidebar);
add(globalToggleButton, BorderLayout.NORTH);
mainPanel = new JPanel();
mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
scrollPane = new JScrollPane(mainPanel);
scrollPane.setBorder(null);
scrollPane.getVerticalScrollBar().setUnitIncrement(10);
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
add(scrollPane, BorderLayout.CENTER);
}
// Public API -----------------------------------------------------------------
public void addApp(DesktopApp app) {
addApp(app, DEFAULT_CATEGORY);
}
public void addApp(DesktopApp app, String category) {
if (!kernel.apps().isRegistered(app.getId())) {
kernel.apps().register(app);
}
SwingUtilities.invokeLater(() -> getCategoryPanel(category).addApp(app));
}
public void loadAppsFromFolder(String folderPath) {
loadAppsFromFolder(folderPath, DEFAULT_CATEGORY);
}
public void loadAppsFromFolder(String folderPath, String category) {
File folder = new File(folderPath);
if (!folder.exists() || !folder.isDirectory()) {
System.err.println("[AppLauncher] Folder not found: " + folderPath);
return;
}
File[] jars = folder.listFiles((dir, name) -> name.endsWith(".jar"));
if (jars == null) return;
for (File jar : jars) loadJarApp(jar, category);
}
public void setCategoryExpanded(String category, boolean expand) {
CategoryPanel panel = categories.get(category);
if (panel != null) panel.setExpanded(expand);
}
// Internal logic ------------------------------------------------------------
private void toggleSidebar(ActionEvent e) {
expanded = !expanded;
globalToggleButton.setText(expanded ? "<<" : ">>");
globalToggleButton.setToolTipText(expanded ? "Collapse sidebar" : "Expand sidebar");
scrollPane.setVisible(expanded);
setPreferredSize(new Dimension(expanded ? EXPANDED_WIDTH : COLLAPSED_WIDTH, 0));
revalidate();
Container parent = getParent();
if (parent instanceof JSplitPane split) {
split.setDividerLocation(expanded ? EXPANDED_WIDTH : COLLAPSED_WIDTH);
}
}
private CategoryPanel getCategoryPanel(String name) {
return categories.computeIfAbsent(name, n -> {
CategoryPanel panel = new CategoryPanel(n);
mainPanel.add(panel);
mainPanel.revalidate();
return panel;
});
}
private void loadJarApp(File jarFile, String category) {
try (URLClassLoader loader = new URLClassLoader(
new URL[]{jarFile.toURI().toURL()},
getClass().getClassLoader())) {
String className = findDesktopAppClass(loader, jarFile);
if (className == null) {
System.err.println("[AppLauncher] No DesktopApp in " + jarFile.getName());
return;
}
Class<?> clazz = Class.forName(className, true, loader);
DesktopApp app = (DesktopApp) clazz.getDeclaredConstructor().newInstance();
addApp(app, category);
System.out.println("[AppLauncher] Loaded: " + app.getName());
} catch (Exception e) {
System.err.println("[AppLauncher] Failed to load " + jarFile.getName());
e.printStackTrace();
}
}
private String findDesktopAppClass(URLClassLoader loader, File jarFile) {
try (JarFile jar = new JarFile(jarFile)) {
Enumeration<JarEntry> entries = jar.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
if (!entry.getName().endsWith(".class")) continue;
String className = entry.getName().replace("/", ".").replace(".class", "");
try {
Class<?> clazz = Class.forName(className, false, loader);
for (Class<?> iface : clazz.getInterfaces()) {
if (iface == DesktopApp.class) return className;
}
} catch (Throwable ignored) {}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
// Inner classes -------------------------------------------------------------
private class CategoryPanel extends JPanel {
private final String categoryName;
private final JButton headerButton;
private final JPanel appsContainer;
private boolean expanded = true;
CategoryPanel(String categoryName) {
this.categoryName = categoryName;
setLayout(new BorderLayout());
setAlignmentX(LEFT_ALIGNMENT);
setBorder(BorderFactory.createEmptyBorder(0, 0, 4, 0));
headerButton = new JButton();
headerButton.setHorizontalAlignment(SwingConstants.LEFT);
headerButton.setFocusPainted(false);
headerButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
headerButton.addActionListener(e -> setExpanded(!expanded));
updateHeaderText();
appsContainer = new JPanel();
appsContainer.setLayout(new BoxLayout(appsContainer, BoxLayout.Y_AXIS));
appsContainer.setBorder(BorderFactory.createEmptyBorder(2, 12, 2, 2));
appsContainer.setVisible(true);
appsContainer.setAlignmentX(LEFT_ALIGNMENT);
add(headerButton, BorderLayout.NORTH);
add(appsContainer, BorderLayout.CENTER);
}
// Override getMaximumSize to allow full width while respecting preferred height
@Override
public Dimension getMaximumSize() {
Dimension preferred = getPreferredSize();
return new Dimension(Integer.MAX_VALUE, preferred.height);
}
private void updateHeaderText() {
String prefix = expanded ? "[-] " : "[+] ";
headerButton.setText(prefix + categoryName);
}
void addApp(DesktopApp app) {
JButton appButton = new JButton(app.getName());
appButton.setHorizontalAlignment(SwingConstants.LEFT);
appButton.setFocusPainted(false);
appButton.setBorder(BorderFactory.createEmptyBorder(4, 8, 4, 8));
appButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
appButton.addActionListener(e -> kernel.apps().launch(app.getId()));
// Make each app button fill the width of its container
appButton.setAlignmentX(LEFT_ALIGNMENT);
appButton.setMaximumSize(new Dimension(Integer.MAX_VALUE, appButton.getPreferredSize().height));
appsContainer.add(appButton);
appsContainer.revalidate();
appsContainer.repaint();
}
void setExpanded(boolean expand) {
if (this.expanded == expand) return;
this.expanded = expand;
appsContainer.setVisible(expanded);
updateHeaderText();
revalidate();
repaint();
}
}
}
@@ -0,0 +1,57 @@
package com.rdesk.kernel;
import javax.swing.*;
import java.util.*;
public class AppManager {
private final Kernel kernel;
private final Map<String, DesktopApp> installedApps = new HashMap<>();
private final Map<String, DesktopApp> runningApps = new HashMap<>();
public AppManager(Kernel kernel) {
this.kernel = kernel;
}
public void register(DesktopApp app) {
installedApps.put(app.getId(), app);
System.out.println("[AppManager] Registered: " + app.getName());
}
public boolean isRegistered(String id) {
return installedApps.containsKey(id);
}
public void launch(String id) {
DesktopApp app = installedApps.get(id);
if (app == null) {
System.err.println("[AppManager] App not found: " + id);
return;
}
JInternalFrame frame = app.createWindow(kernel);
frame.putClientProperty("appId", id);
runningApps.put(id, app);
kernel.windows().open(frame);
app.onStart(kernel);
System.out.println("[AppManager] Launched: " + app.getName());
}
public void stopAll() {
for (DesktopApp app : runningApps.values()) {
try {
app.onStop(kernel);
System.out.println("[AppManager] Stopped: " + app.getName());
} catch (Exception e) {
e.printStackTrace();
}
}
runningApps.clear();
}
public DesktopApp getApp(String id) {
return installedApps.get(id);
}
}
@@ -0,0 +1,20 @@
package com.rdesk.kernel;
import javax.swing.*;
public interface DesktopApp {
String getId();
String getName();
default ImageIcon getIcon() {
return null;
}
JInternalFrame createWindow(Kernel kernel);
default void onStart(Kernel kernel) {}
default void onStop(Kernel kernel) {}
}
@@ -0,0 +1,180 @@
package com.rdesk.kernel;
import javax.swing.*;
import java.awt.*;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.jar.*;
public class DesktopIconManager {
private final Kernel kernel;
private final JDesktopPane desktop;
private final int cellWidth = 110;
private final int cellHeight = 110;
private final int iconWidth = 90;
private final int iconHeight = 70;
/** Tracks how many icons have been placed so far. */
private int index = 0;
public DesktopIconManager(Kernel kernel, JDesktopPane desktop) {
this.kernel = kernel;
this.desktop = desktop;
this.desktop.setLayout(null);
}
// ------------------------------------------------------------------ //
// Public API //
// ------------------------------------------------------------------ //
/**
* Scan a folder for {@code .jar} files and create a desktop icon for
* every {@link DesktopApp} implementation found inside them.
*/
public void loadAppsFromFolder(String path) {
File folder = new File(path);
if (!folder.exists() || !folder.isDirectory()) {
System.out.println("[IconManager] applications folder not found: " + path);
return;
}
File[] jars = folder.listFiles((d, name) -> name.endsWith(".jar"));
if (jars == null) return;
for (File jar : jars) {
loadJar(jar);
}
}
/**
* Directly add a {@link DesktopApp} icon to the desktop.
* Use this for system / built-in apps registered from {@code main()}.
*/
public void addApp(DesktopApp app) {
SwingUtilities.invokeLater(() -> createIcon(app));
}
// ------------------------------------------------------------------ //
// JAR loading //
// ------------------------------------------------------------------ //
private void loadJar(File jarFile) {
// URLClassLoader implements Closeable — always close it when done
try (URLClassLoader loader = new URLClassLoader(
new URL[]{jarFile.toURI().toURL()},
getClass().getClassLoader())) {
String className = findAppClass(loader, jarFile);
if (className == null) {
System.out.println("[IconManager] No DesktopApp found in " + jarFile.getName());
return;
}
Class<?> clazz = Class.forName(className, true, loader);
DesktopApp app = (DesktopApp) clazz.getDeclaredConstructor().newInstance();
SwingUtilities.invokeLater(() -> createIcon(app));
System.out.println("[IconManager] Loaded: " + app.getName());
} catch (Exception e) {
System.err.println("[IconManager] Failed to load " + jarFile.getName() + ": " + e.getMessage());
e.printStackTrace();
}
}
private String findAppClass(URLClassLoader loader, File jarFile) {
try (JarFile jar = new JarFile(jarFile)) {
var entries = jar.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
if (!entry.getName().endsWith(".class")) continue;
String className = entry.getName()
.replace("/", ".")
.replace(".class", "");
try {
Class<?> clazz = Class.forName(className, false, loader);
for (Class<?> iface : clazz.getInterfaces()) {
if (iface == DesktopApp.class) {
return className;
}
}
} catch (Throwable ignored) {}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
// ------------------------------------------------------------------ //
// Icon creation & layout //
// ------------------------------------------------------------------ //
/**
* Calculate the next grid position.
*
* Column count is derived lazily from the desktop's current width so it
* remains correct even when the frame has been packed / resized before
* icons are added. Falls back to 1 column if the pane has no width yet.
*/
private Point nextGridPosition() {
int desktopWidth = desktop.getWidth();
int cols = (desktopWidth > 0) ? Math.max(1, desktopWidth / cellWidth) : 1;
int row = index / cols;
int col = index % cols;
int x = col * cellWidth + (cellWidth - iconWidth) / 2;
int y = row * cellHeight + (cellHeight - iconHeight) / 2;
index++;
return new Point(x, y);
}
private void createIcon(DesktopApp app) {
JButton button = new JButton("<html><center>" + app.getName() + "</center></html>");
// Style — a clean, dark desktop-icon look
button.setFont(new Font("Segoe UI", Font.PLAIN, 11));
button.setForeground(Color.WHITE);
button.setBackground(new Color(40, 40, 60, 200));
button.setBorderPainted(false);
button.setFocusPainted(false);
button.setOpaque(true);
button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
button.setToolTipText(app.getName());
button.addMouseListener(new java.awt.event.MouseAdapter() {
@Override public void mouseEntered(java.awt.event.MouseEvent e) {
button.setBackground(new Color(70, 70, 100, 220));
}
@Override public void mouseExited(java.awt.event.MouseEvent e) {
button.setBackground(new Color(40, 40, 60, 200));
}
});
Point p = nextGridPosition();
button.setBounds(p.x, p.y, iconWidth, iconHeight);
button.addActionListener(e -> {
// app may be pre registered, so dont do shit
if (!kernel.apps().isRegistered(app.getId())) {
kernel.apps().register(app);
}
kernel.apps().launch(app.getId());
});
desktop.add(button);
desktop.repaint();
}
}
@@ -0,0 +1,26 @@
package com.rdesk.kernel;
import java.util.*;
import java.util.function.Consumer;
public class EventBus {
private final Map<String, List<Consumer<Object>>> listeners = new HashMap<>();
public void on(String event, Consumer<Object> handler) {
listeners.computeIfAbsent(event, k -> new ArrayList<>()).add(handler);
}
public void emit(String event) {
emit(event, null);
}
public void emit(String event, Object data) {
List<Consumer<Object>> list = listeners.get(event);
if (list == null) return;
for (Consumer<Object> h : list) {
h.accept(data);
}
}
}
+109
View File
@@ -0,0 +1,109 @@
package com.rdesk.kernel;
import javax.swing.*;
public class Kernel {
private static volatile Kernel instance;
private final AppManager appManager;
private final WindowManager windowManager;
private final EventBus eventBus;
private final ServiceRegistry services;
private DesktopIconManager iconManager;
private JFrame desktopFrame;
private boolean running = false;
private Kernel() {
this.eventBus = new EventBus();
this.services = new ServiceRegistry();
this.appManager = new AppManager(this);
this.windowManager = new WindowManager(this);
}
/** Thread-safe singleton accessor. */
public static Kernel get() {
if (instance == null) {
synchronized (Kernel.class) {
if (instance == null) {
instance = new Kernel();
}
}
}
return instance;
}
public void boot() {
running = true;
System.out.println("[Kernel] Booting...");
eventBus.emit("kernel.boot");
}
public void attachFrame(JFrame frame) {
this.desktopFrame = frame;
}
/**
* Attach the icon manager so the kernel can delegate system-app
* registration to it. Call this after the desktop pane is ready.
*/
public void attachIconManager(DesktopIconManager iconManager) {
this.iconManager = iconManager;
}
// ------------------------------------------------------------------ //
// System-app API — call from main() before or after boot() //
// ------------------------------------------------------------------ //
/**
* Register a built-in / system app and place its icon on the desktop.
*
* <pre>{@code
* Kernel kernel = Kernel.get();
* kernel.registerSystemApp(new MyCalculatorApp());
* }</pre>
*
* @param app the {@link DesktopApp} implementation to register
* @throws IllegalStateException if {@link #attachIconManager} has not been called yet
*/
public void registerSystemApp(DesktopApp app) {
if (iconManager == null) {
throw new IllegalStateException(
"[Kernel] attachIconManager() must be called before registerSystemApp()");
}
appManager.register(app);
iconManager.addApp(app);
System.out.println("[Kernel] System app registered: " + app.getName());
}
public void shutdown() {
if (!running) return;
System.out.println("[Kernel] Shutdown started");
running = false;
appManager.stopAll();
windowManager.closeAll();
if (desktopFrame != null) {
desktopFrame.dispose();
}
eventBus.emit("kernel.shutdown");
System.out.println("[Kernel] Shutdown complete");
System.exit(0);
}
// ------------------------------------------------------------------ //
// Accessors //
// ------------------------------------------------------------------ //
public AppManager apps() { return appManager; }
public WindowManager windows() { return windowManager; }
public EventBus events() { return eventBus; }
public ServiceRegistry services() { return services; }
public boolean isRunning() { return running; }
public JFrame getDesktopFrame() { return desktopFrame; }
}
@@ -0,0 +1,16 @@
package com.rdesk.kernel;
import java.util.*;
public class ServiceRegistry {
private final Map<String, Object> services = new HashMap<>();
public void register(String name, Object service) {
services.put(name, service);
}
public <T> T get(String name, Class<T> type) {
return type.cast(services.get(name));
}
}
@@ -0,0 +1,55 @@
package com.rdesk.kernel;
import javax.swing.*;
import java.util.ArrayList;
import java.util.List;
public class WindowManager {
private final Kernel kernel;
private JDesktopPane desktop;
public WindowManager(Kernel kernel) {
this.kernel = kernel;
}
public void attachDesktop(JDesktopPane desktop) {
this.desktop = desktop;
}
public void open(JInternalFrame frame) {
desktop.add(frame);
frame.setVisible(true);
}
public void closeAll() {
if (desktop == null) return;
for (JInternalFrame frame : desktop.getAllFrames()) {
try {
frame.setClosed(true);
} catch (Exception ignored) {}
}
}
public JInternalFrame[] getAllWindows() {
if (desktop == null) return new JInternalFrame[0];
return desktop.getAllFrames();
}
public void closeAllByAppId(String appId) {
if (desktop == null) return;
for (JInternalFrame frame : desktop.getAllFrames()) {
Object prop = frame.getClientProperty("appId");
if (appId.equals(prop)) {
try {
frame.setClosed(true);
} catch (Exception ignored) {}
}
}
}
public JDesktopPane getDesktop() {
// allows to change stuff for the desktop pane, essential for our bgchanger class
return desktop;
}
}