experimenting with sessions and stuff like that.
Changes: + Login Panel + Some Kernel Booting stuff
This commit is contained in:
@@ -1,103 +1,9 @@
|
|||||||
package com.rdesk;
|
package com.rdesk;
|
||||||
|
|
||||||
import com.rdesk.applications.entertainment.comic.Xkcd;
|
import com.rdesk.applications.system.login.Login;
|
||||||
import com.rdesk.applications.productivity.Calculator;
|
|
||||||
import com.rdesk.applications.productivity.browsing.TabbedBrowser;
|
|
||||||
import com.rdesk.applications.productivity.code.CodeDesk;
|
|
||||||
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.applications.system.rsys.KbChanger;
|
|
||||||
import com.rdesk.kernel.*;
|
|
||||||
import com.rdesk.kernel.launcher.AppLauncher;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
|
|
||||||
public class Main {
|
public class Main {
|
||||||
|
|
||||||
private static final boolean RUN_IN_FS = false;
|
|
||||||
private static final boolean USE_SYSDEFAULT_LAF = false;
|
|
||||||
private static final boolean USE_FLATLAF = false;
|
|
||||||
private static final boolean loadSettingsKernelFlag = true;
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SwingUtilities.invokeLater(Main::boot);
|
Login.main(args);
|
||||||
}
|
|
||||||
|
|
||||||
private static void boot() {
|
|
||||||
Kernel kernel = Kernel.get();
|
|
||||||
kernel.initSettings(loadSettingsKernelFlag);
|
|
||||||
kernel.boot();
|
|
||||||
JFrame frame = new JFrame("rDesk");
|
|
||||||
|
|
||||||
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) {
|
|
||||||
// productivity
|
|
||||||
launcher.addApp(new Notes(), "Productivity");
|
|
||||||
launcher.addApp(new TabbedBrowser(), "Productivity");
|
|
||||||
launcher.addApp(new CodeDesk(), "Productivity");
|
|
||||||
launcher.addApp(new Calculator(), "Productivity");
|
|
||||||
// entertainment
|
|
||||||
launcher.addApp(new Xkcd(), "Entertainment");
|
|
||||||
// System apps
|
|
||||||
launcher.addApp(new Exit(), "System");
|
|
||||||
launcher.addApp(new TaskManager(), "System");
|
|
||||||
launcher.addApp(new FileExplorerApp(), "System");
|
|
||||||
launcher.addApp(new KbChanger(), "System");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
package com.rdesk.applications.system;
|
package com.rdesk.applications.system;
|
||||||
|
|
||||||
|
import com.rdesk.applications.system.login.Login;
|
||||||
import com.rdesk.kernel.DesktopApp;
|
import com.rdesk.kernel.DesktopApp;
|
||||||
import com.rdesk.kernel.Kernel;
|
import com.rdesk.kernel.Kernel;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.awt.Window;
|
||||||
|
|
||||||
public class Exit implements DesktopApp {
|
public class Exit implements DesktopApp {
|
||||||
|
|
||||||
@@ -22,7 +24,7 @@ public class Exit implements DesktopApp {
|
|||||||
@Override
|
@Override
|
||||||
public JInternalFrame createWindow(Kernel kernel) {
|
public JInternalFrame createWindow(Kernel kernel) {
|
||||||
JInternalFrame frame = new JInternalFrame("System Controls", true, true);
|
JInternalFrame frame = new JInternalFrame("System Controls", true, true);
|
||||||
frame.setSize(350, 180);
|
frame.setSize(420, 240);
|
||||||
frame.setResizable(false);
|
frame.setResizable(false);
|
||||||
|
|
||||||
JPanel mainPanel = new JPanel(new GridBagLayout());
|
JPanel mainPanel = new JPanel(new GridBagLayout());
|
||||||
@@ -30,7 +32,7 @@ public class Exit implements DesktopApp {
|
|||||||
GridBagConstraints gbc = new GridBagConstraints();
|
GridBagConstraints gbc = new GridBagConstraints();
|
||||||
gbc.gridwidth = GridBagConstraints.REMAINDER;
|
gbc.gridwidth = GridBagConstraints.REMAINDER;
|
||||||
gbc.fill = GridBagConstraints.HORIZONTAL;
|
gbc.fill = GridBagConstraints.HORIZONTAL;
|
||||||
gbc.insets = new Insets(5, 5, 5, 5);
|
gbc.insets = new Insets(6, 6, 6, 6);
|
||||||
|
|
||||||
JLabel label = new JLabel("Choose an action:");
|
JLabel label = new JLabel("Choose an action:");
|
||||||
label.setFont(label.getFont().deriveFont(Font.BOLD));
|
label.setFont(label.getFont().deriveFont(Font.BOLD));
|
||||||
@@ -38,8 +40,11 @@ public class Exit implements DesktopApp {
|
|||||||
|
|
||||||
JButton shutdownButton = new JButton("Shutdown rDesk");
|
JButton shutdownButton = new JButton("Shutdown rDesk");
|
||||||
JButton shutdownOS = new JButton("Shutdown Host OS");
|
JButton shutdownOS = new JButton("Shutdown Host OS");
|
||||||
|
JButton logoffButton = new JButton("Log off");
|
||||||
|
|
||||||
shutdownButton.setFocusPainted(false);
|
shutdownButton.setFocusPainted(false);
|
||||||
shutdownButton.addActionListener(e -> confirmAndShutdown(kernel, frame));
|
shutdownButton.addActionListener(e -> confirmAndShutdown(kernel, frame));
|
||||||
|
|
||||||
shutdownOS.setFocusPainted(false);
|
shutdownOS.setFocusPainted(false);
|
||||||
shutdownOS.addActionListener(e -> {
|
shutdownOS.addActionListener(e -> {
|
||||||
try {
|
try {
|
||||||
@@ -48,8 +53,38 @@ public class Exit implements DesktopApp {
|
|||||||
throw new RuntimeException(ex);
|
throw new RuntimeException(ex);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
logoffButton.setFocusPainted(false);
|
||||||
|
|
||||||
|
logoffButton.addActionListener(e -> {
|
||||||
|
int choice = JOptionPane.showConfirmDialog(
|
||||||
|
frame,
|
||||||
|
"Log off and return to the login screen?"
|
||||||
|
+ "This will end all running Tasks!",
|
||||||
|
"Confirm Log off",
|
||||||
|
JOptionPane.YES_NO_OPTION,
|
||||||
|
JOptionPane.WARNING_MESSAGE
|
||||||
|
);
|
||||||
|
if (choice != JOptionPane.YES_OPTION) return;
|
||||||
|
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
try {
|
||||||
|
Login.main(null);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
JOptionPane.showMessageDialog(frame,
|
||||||
|
"Failed to launch login screen: " + ex.getMessage(),
|
||||||
|
"Error",
|
||||||
|
JOptionPane.ERROR_MESSAGE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
kernel.logoff();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
mainPanel.add(shutdownButton, gbc);
|
mainPanel.add(shutdownButton, gbc);
|
||||||
mainPanel.add(shutdownOS, gbc);
|
mainPanel.add(shutdownOS, gbc);
|
||||||
|
mainPanel.add(logoffButton, gbc);
|
||||||
|
|
||||||
JButton cancelButton = new JButton("Cancel");
|
JButton cancelButton = new JButton("Cancel");
|
||||||
cancelButton.setFocusPainted(false);
|
cancelButton.setFocusPainted(false);
|
||||||
@@ -89,11 +124,11 @@ public class Exit implements DesktopApp {
|
|||||||
"Are you sure? This will shutdown the Host OS",
|
"Are you sure? This will shutdown the Host OS",
|
||||||
JOptionPane.YES_NO_OPTION,
|
JOptionPane.YES_NO_OPTION,
|
||||||
JOptionPane.WARNING_MESSAGE
|
JOptionPane.WARNING_MESSAGE
|
||||||
);
|
);
|
||||||
|
|
||||||
if (choice == JOptionPane.YES_OPTION) {
|
if (choice == JOptionPane.YES_OPTION) {
|
||||||
frame.dispose();
|
frame.dispose();
|
||||||
performShutdownCmd(); // cause else the call wouldnt be called
|
performShutdownCmd();
|
||||||
kernel.shutdown();
|
kernel.shutdown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -107,8 +142,8 @@ public class Exit implements DesktopApp {
|
|||||||
JOptionPane.WARNING_MESSAGE
|
JOptionPane.WARNING_MESSAGE
|
||||||
);
|
);
|
||||||
if (choice == JOptionPane.YES_OPTION) {
|
if (choice == JOptionPane.YES_OPTION) {
|
||||||
frame.dispose(); // close the confirmation window first
|
frame.dispose();
|
||||||
kernel.shutdown();
|
kernel.shutdown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
|
||||||
|
package com.rdesk.applications.system.login;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
import javax.crypto.SecretKeyFactory;
|
||||||
|
import javax.crypto.spec.PBEKeySpec;
|
||||||
|
import java.awt.Window;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class Handler {
|
||||||
|
private static final String FILENAME = System.getProperty("user.home") + File.separator + "rsys_passwdstore.json";
|
||||||
|
private static final SecureRandom RNG = new SecureRandom();
|
||||||
|
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
||||||
|
private static final int SALT_LEN = 16;
|
||||||
|
private static final int KEY_LEN = 256; // bits
|
||||||
|
private static final int ITERATIONS = 200_000;
|
||||||
|
private static final String ALGO = "PBKDF2WithHmacSHA256";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static boolean storeExists() {
|
||||||
|
return new File(FILENAME).exists();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UserRecord load() throws Exception {
|
||||||
|
File f = new File(FILENAME);
|
||||||
|
if (!f.exists()) return null;
|
||||||
|
try (FileReader fr = new FileReader(f)) {
|
||||||
|
return GSON.fromJson(fr, UserRecord.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void save(UserRecord u) throws Exception {
|
||||||
|
try (FileWriter fw = new FileWriter(FILENAME)) {
|
||||||
|
GSON.toJson(u, fw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UserRecord createUser(String username, char[] password) throws Exception {
|
||||||
|
byte[] salt = new byte[SALT_LEN];
|
||||||
|
RNG.nextBytes(salt);
|
||||||
|
byte[] hash = pbkdf2(password, salt, ITERATIONS, KEY_LEN);
|
||||||
|
UserRecord u = new UserRecord();
|
||||||
|
u.username = username;
|
||||||
|
u.saltB64 = Base64.getEncoder().encodeToString(salt);
|
||||||
|
u.hashB64 = Base64.getEncoder().encodeToString(hash);
|
||||||
|
u.iterations = ITERATIONS;
|
||||||
|
return u;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean verifyPassword(char[] password, UserRecord stored) throws Exception {
|
||||||
|
byte[] salt = Base64.getDecoder().decode(stored.saltB64);
|
||||||
|
byte[] expected = Base64.getDecoder().decode(stored.hashB64);
|
||||||
|
byte[] computed = pbkdf2(password, salt, stored.iterations, expected.length * 8);
|
||||||
|
return slowEquals(expected, computed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean verifyAndClose(Window window, char[] password, UserRecord stored) throws Exception {
|
||||||
|
try {
|
||||||
|
boolean ok = verifyPassword(password, stored);
|
||||||
|
if (ok && window != null) {
|
||||||
|
window.dispose();
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
} finally {
|
||||||
|
clearPassword(password);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] pbkdf2(char[] password, byte[] salt, int iterations, int keyLengthBits) throws Exception {
|
||||||
|
PBEKeySpec spec = new PBEKeySpec(password, salt, iterations, keyLengthBits);
|
||||||
|
SecretKeyFactory skf = SecretKeyFactory.getInstance(ALGO);
|
||||||
|
return skf.generateSecret(spec).getEncoded();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean slowEquals(byte[] a, byte[] b) {
|
||||||
|
if (a == null || b == null || a.length != b.length) return false;
|
||||||
|
int diff = 0;
|
||||||
|
for (int i = 0; i < a.length; i++) diff |= a[i] ^ b[i];
|
||||||
|
return diff == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void clearPassword(char[] p) {
|
||||||
|
if (p == null) return;
|
||||||
|
Arrays.fill(p, (char) 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String[] listUsers() {
|
||||||
|
File f = new File(FILENAME);
|
||||||
|
if (!f.exists()) return new String[0];
|
||||||
|
try (FileReader fr = new FileReader(f)) {
|
||||||
|
JsonElement root = JsonParser.parseReader(fr);
|
||||||
|
List<String> users = new ArrayList<>();
|
||||||
|
if (root.isJsonArray()) {
|
||||||
|
JsonArray arr = root.getAsJsonArray();
|
||||||
|
for (JsonElement el : arr) {
|
||||||
|
try {
|
||||||
|
UserRecord ur = GSON.fromJson(el, UserRecord.class);
|
||||||
|
if (ur != null && ur.username != null) users.add(ur.username);
|
||||||
|
} catch (Throwable ignore) { /* skip malformed entry */ }
|
||||||
|
}
|
||||||
|
} else if (root.isJsonObject()) {
|
||||||
|
try {
|
||||||
|
UserRecord ur = GSON.fromJson(root, UserRecord.class);
|
||||||
|
if (ur != null && ur.username != null) users.add(ur.username);
|
||||||
|
} catch (Throwable ignore) { /* malformed object -> no users */ }
|
||||||
|
}
|
||||||
|
return users.toArray(new String[0]);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return new String[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,267 @@
|
|||||||
|
package com.rdesk.applications.system.login;
|
||||||
|
|
||||||
|
import com.rdesk.kernel.BootMgr;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
public class Login {
|
||||||
|
private JFrame frame;
|
||||||
|
private JComboBox<String> userCombo;
|
||||||
|
private JTextField userFieldFallback;
|
||||||
|
private JPasswordField passField;
|
||||||
|
private JLabel statusLabel;
|
||||||
|
private UserRecord stored;
|
||||||
|
private JButton loginBtn;
|
||||||
|
private JButton closeBtn;
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
try {
|
||||||
|
new Login().start();
|
||||||
|
} catch (Exception e) {
|
||||||
|
JOptionPane.showMessageDialog(null, e);
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void start() throws Exception {
|
||||||
|
log("start(): checking store existence");
|
||||||
|
if (!Handler.storeExists()) {
|
||||||
|
showCreateUserDialog();
|
||||||
|
}
|
||||||
|
stored = Handler.load();
|
||||||
|
log("start(): loaded user record: " + (stored != null ? stored.username : "null"));
|
||||||
|
buildAndShowGui();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void buildAndShowGui() {
|
||||||
|
log("buildAndShowGui(): building GUI");
|
||||||
|
frame = new JFrame("rDesk Login");
|
||||||
|
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||||
|
frame.setResizable(false);
|
||||||
|
|
||||||
|
Image icon = createImageFromSwingIcon(UIManager.getIcon("FileView.computerIcon"));
|
||||||
|
if (icon != null) frame.setIconImage(icon);
|
||||||
|
|
||||||
|
JPanel panel = new JPanel(new GridBagLayout());
|
||||||
|
GridBagConstraints c = new GridBagConstraints();
|
||||||
|
c.insets = new Insets(6, 6, 6, 6);
|
||||||
|
c.anchor = GridBagConstraints.WEST;
|
||||||
|
c.fill = GridBagConstraints.HORIZONTAL;
|
||||||
|
|
||||||
|
int row = 0;
|
||||||
|
|
||||||
|
if (icon != null) {
|
||||||
|
JLabel iconLabel = new JLabel(new ImageIcon(icon.getScaledInstance(64, 64, Image.SCALE_SMOOTH)));
|
||||||
|
GridBagConstraints ic = (GridBagConstraints) c.clone();
|
||||||
|
ic.gridx = 0;
|
||||||
|
ic.gridy = row;
|
||||||
|
ic.gridheight = 2;
|
||||||
|
ic.anchor = GridBagConstraints.CENTER;
|
||||||
|
panel.add(iconLabel, ic);
|
||||||
|
}
|
||||||
|
|
||||||
|
int colOffset = (icon != null) ? 1 : 0;
|
||||||
|
|
||||||
|
c.gridx = colOffset;
|
||||||
|
c.gridy = row;
|
||||||
|
c.gridwidth = 1;
|
||||||
|
panel.add(new JLabel("Username:"), c);
|
||||||
|
|
||||||
|
c.gridx = colOffset + 1;
|
||||||
|
c.gridy = row++;
|
||||||
|
c.weightx = 1.0;
|
||||||
|
String[] users = fetchUserList();
|
||||||
|
if (users.length > 0) {
|
||||||
|
userCombo = new JComboBox<>(users);
|
||||||
|
userCombo.setEditable(false);
|
||||||
|
userCombo.setPreferredSize(new Dimension(160, 24));
|
||||||
|
panel.add(userCombo, c);
|
||||||
|
} else {
|
||||||
|
userFieldFallback = new JTextField(16);
|
||||||
|
userFieldFallback.setPreferredSize(new Dimension(160, 24));
|
||||||
|
panel.add(userFieldFallback, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
c.gridx = colOffset;
|
||||||
|
c.gridy = row;
|
||||||
|
c.weightx = 0;
|
||||||
|
panel.add(new JLabel("Password:"), c);
|
||||||
|
|
||||||
|
c.gridx = colOffset + 1;
|
||||||
|
c.gridy = row++;
|
||||||
|
c.weightx = 1.0;
|
||||||
|
passField = new JPasswordField(16);
|
||||||
|
passField.setPreferredSize(new Dimension(160, 24));
|
||||||
|
panel.add(passField, c);
|
||||||
|
|
||||||
|
closeBtn = new JButton("Close");
|
||||||
|
closeBtn.addActionListener(e -> frame.dispose());
|
||||||
|
loginBtn = new JButton("Login");
|
||||||
|
loginBtn.addActionListener(e -> onLogin());
|
||||||
|
|
||||||
|
c.gridx = colOffset;
|
||||||
|
c.gridy = row;
|
||||||
|
c.gridwidth = 1;
|
||||||
|
c.weightx = 0.5;
|
||||||
|
c.fill = GridBagConstraints.HORIZONTAL;
|
||||||
|
panel.add(closeBtn, c);
|
||||||
|
|
||||||
|
c.gridx = colOffset + 1;
|
||||||
|
c.gridy = row++;
|
||||||
|
c.weightx = 0.5;
|
||||||
|
panel.add(loginBtn, c);
|
||||||
|
|
||||||
|
statusLabel = new JLabel(" ");
|
||||||
|
statusLabel.setForeground(Color.BLUE);
|
||||||
|
c.gridx = colOffset;
|
||||||
|
c.gridy = row;
|
||||||
|
c.gridwidth = 2;
|
||||||
|
c.weightx = 1.0;
|
||||||
|
c.fill = GridBagConstraints.HORIZONTAL;
|
||||||
|
panel.add(statusLabel, c);
|
||||||
|
|
||||||
|
frame.getContentPane().add(panel);
|
||||||
|
frame.getRootPane().setDefaultButton(loginBtn);
|
||||||
|
|
||||||
|
passField.addActionListener(e -> onLogin());
|
||||||
|
if (userFieldFallback != null) userFieldFallback.addActionListener(e -> onLogin());
|
||||||
|
if (userCombo != null) {
|
||||||
|
userCombo.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke("ENTER"), "login");
|
||||||
|
userCombo.getActionMap().put("login", new AbstractAction() {
|
||||||
|
@Override public void actionPerformed(ActionEvent e) { onLogin(); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
frame.pack();
|
||||||
|
Dimension total = frame.getContentPane().getSize();
|
||||||
|
int gap = 12;
|
||||||
|
int buttonWidth = Math.max(90, (total.width - gap) / 2);
|
||||||
|
closeBtn.setPreferredSize(new Dimension(buttonWidth, 28));
|
||||||
|
loginBtn.setPreferredSize(new Dimension(buttonWidth, 28));
|
||||||
|
frame.pack();
|
||||||
|
|
||||||
|
frame.setLocationRelativeTo(null);
|
||||||
|
frame.setVisible(true);
|
||||||
|
log("buildAndShowGui(): GUI visible");
|
||||||
|
}
|
||||||
|
|
||||||
|
private Image createImageFromSwingIcon(Icon icon) {
|
||||||
|
if (icon == null) return null;
|
||||||
|
int w = Math.max(1, icon.getIconWidth());
|
||||||
|
int h = Math.max(1, icon.getIconHeight());
|
||||||
|
BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
|
||||||
|
Graphics2D g = img.createGraphics();
|
||||||
|
try {
|
||||||
|
icon.paintIcon(null, g, 0, 0);
|
||||||
|
} finally {
|
||||||
|
g.dispose();
|
||||||
|
}
|
||||||
|
return img;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onLogin() {
|
||||||
|
String userAttempt = (userCombo != null)
|
||||||
|
? String.valueOf(userCombo.getSelectedItem()).trim()
|
||||||
|
: userFieldFallback.getText().trim();
|
||||||
|
char[] passAttempt = passField.getPassword();
|
||||||
|
|
||||||
|
log("onLogin(): attempt for user='" + userAttempt + "'");
|
||||||
|
|
||||||
|
if (stored == null) {
|
||||||
|
statusLabel.setText("No user record found.");
|
||||||
|
Handler.clearPassword(passAttempt);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stored.username.equals(userAttempt)) {
|
||||||
|
statusLabel.setText("Invalid username or password.");
|
||||||
|
Handler.clearPassword(passAttempt);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
boolean ok = Handler.verifyAndClose(frame, passAttempt, stored);
|
||||||
|
if (ok) {
|
||||||
|
log("onLogin(): authentication successful for user='" + userAttempt + "'");
|
||||||
|
statusLabel.setText(" ");
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
new SwingWorker<Void, Void>() {
|
||||||
|
@Override protected Void doInBackground() {
|
||||||
|
try {
|
||||||
|
BootMgr.launchOrShow();
|
||||||
|
log("[Login]: BootManager.launchOrShow() completed");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log("onLogin(): BootManager error: " + e.getMessage());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}.execute();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
statusLabel.setText("Invalid username or password.");
|
||||||
|
log("onLogin(): password verification failed for user='" + userAttempt + "'");
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
statusLabel.setText("Error during verification.");
|
||||||
|
log("onLogin(): exception during verification: " + ex.getMessage());
|
||||||
|
} finally {
|
||||||
|
Handler.clearPassword(passAttempt);
|
||||||
|
passField.setText("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showCreateUserDialog() throws Exception {
|
||||||
|
log("showCreateUserDialog(): creating user dialog");
|
||||||
|
JPanel p = new JPanel(new GridLayout(3,2,6,6));
|
||||||
|
JTextField user = new JTextField(16);
|
||||||
|
JPasswordField pass = new JPasswordField(16);
|
||||||
|
JPasswordField pass2 = new JPasswordField(16);
|
||||||
|
p.add(new JLabel("Username:")); p.add(user);
|
||||||
|
p.add(new JLabel("Password:")); p.add(pass);
|
||||||
|
p.add(new JLabel("Confirm:")); p.add(pass2);
|
||||||
|
|
||||||
|
int res = JOptionPane.showConfirmDialog(null, p, "Create User", JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE);
|
||||||
|
if (res != JOptionPane.OK_OPTION) {
|
||||||
|
log("showCreateUserDialog(): user cancelled creation, exiting");
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
if (!String.valueOf(pass.getPassword()).equals(String.valueOf(pass2.getPassword()))) {
|
||||||
|
JOptionPane.showMessageDialog(null, "Passwords do not match.", "Error", JOptionPane.ERROR_MESSAGE);
|
||||||
|
log("showCreateUserDialog(): passwords did not match");
|
||||||
|
showCreateUserDialog();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
UserRecord u = Handler.createUser(user.getText().trim(), pass.getPassword());
|
||||||
|
Handler.save(u);
|
||||||
|
log("showCreateUserDialog(): created and saved user '" + u.username + "'");
|
||||||
|
JOptionPane.showMessageDialog(null, "User created.", "Success", JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String[] fetchUserList() {
|
||||||
|
try {
|
||||||
|
log("fetchUserList(): attempting to retrieve user list from Handler");
|
||||||
|
String[] users = Handler.listUsers();
|
||||||
|
if (users == null || users.length == 0) {
|
||||||
|
if (stored != null) {
|
||||||
|
return new String[]{stored.username};
|
||||||
|
}
|
||||||
|
return new String[0];
|
||||||
|
}
|
||||||
|
return users;
|
||||||
|
} catch (Exception t) {
|
||||||
|
if (stored != null) return new String[]{stored.username};
|
||||||
|
return new String[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void log(String msg) {
|
||||||
|
System.out.println("[Login]: " + msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.rdesk.applications.system.login;
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
public class UserRecord {
|
||||||
|
@SerializedName("username") public String username;
|
||||||
|
@SerializedName("salt_b64") public String saltB64;
|
||||||
|
@SerializedName("hash_b64") public String hashB64;
|
||||||
|
@SerializedName("iterations") public int iterations;
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.rdesk.kernel;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
|
||||||
|
public final class BootMgr {
|
||||||
|
private static JFrame desktopFrame;
|
||||||
|
private BootMgr() {}
|
||||||
|
|
||||||
|
public static synchronized void launchOrShow() {
|
||||||
|
if (desktopFrame == null) {
|
||||||
|
JFrame f = BootProc.boot();
|
||||||
|
if (f != null) desktopFrame = f;
|
||||||
|
else {
|
||||||
|
desktopFrame = new JFrame();
|
||||||
|
desktopFrame.setContentPane(f);
|
||||||
|
desktopFrame.pack();
|
||||||
|
}
|
||||||
|
desktopFrame.setVisible(true);
|
||||||
|
desktopFrame.toFront();
|
||||||
|
} else {
|
||||||
|
if (!desktopFrame.isVisible()) desktopFrame.setVisible(true);
|
||||||
|
desktopFrame.toFront();
|
||||||
|
desktopFrame.requestFocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static synchronized void hideDesktopOnLogout() {
|
||||||
|
if (desktopFrame != null) {
|
||||||
|
desktopFrame.setVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static synchronized void disposeDesktop() {
|
||||||
|
if (desktopFrame != null) {
|
||||||
|
desktopFrame.dispose();
|
||||||
|
desktopFrame = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
package com.rdesk.kernel;
|
||||||
|
|
||||||
|
import com.rdesk.applications.entertainment.comic.Xkcd;
|
||||||
|
import com.rdesk.applications.productivity.Calculator;
|
||||||
|
import com.rdesk.applications.productivity.Notes;
|
||||||
|
import com.rdesk.applications.productivity.browsing.TabbedBrowser;
|
||||||
|
import com.rdesk.applications.productivity.code.CodeDesk;
|
||||||
|
import com.rdesk.applications.system.Exit;
|
||||||
|
import com.rdesk.applications.system.TaskManager;
|
||||||
|
import com.rdesk.applications.system.filehaj.FileExplorerApp;
|
||||||
|
import com.rdesk.applications.system.rsys.KbChanger;
|
||||||
|
import com.rdesk.kernel.launcher.AppLauncher;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
|
||||||
|
public class BootProc {
|
||||||
|
private static final boolean RUN_IN_FS = true;
|
||||||
|
private static final boolean USE_SYSDEFAULT_LAF = false;
|
||||||
|
private static final boolean USE_FLATLAF = false;
|
||||||
|
private static final boolean loadSettingsKernelFlag = true;
|
||||||
|
|
||||||
|
private static JFrame mainFrame;
|
||||||
|
private static JDesktopPane desktopPane;
|
||||||
|
|
||||||
|
public static synchronized JFrame boot() {
|
||||||
|
if (mainFrame != null) {
|
||||||
|
if (!mainFrame.isVisible()) mainFrame.setVisible(true);
|
||||||
|
mainFrame.toFront();
|
||||||
|
mainFrame.requestFocus();
|
||||||
|
return mainFrame;
|
||||||
|
}
|
||||||
|
|
||||||
|
Kernel kernel = Kernel.get();
|
||||||
|
kernel.initSettings(loadSettingsKernelFlag);
|
||||||
|
kernel.boot();
|
||||||
|
|
||||||
|
mainFrame = new JFrame("rDesk");
|
||||||
|
mainFrame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
|
||||||
|
mainFrame.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) {
|
||||||
|
mainFrame.setUndecorated(true);
|
||||||
|
mainFrame.setExtendedState(JFrame.MAXIMIZED_BOTH);
|
||||||
|
} else {
|
||||||
|
mainFrame.setSize(1200, 800);
|
||||||
|
mainFrame.setLocationRelativeTo(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
desktopPane = new JDesktopPane();
|
||||||
|
kernel.windows().attachDesktop(desktopPane);
|
||||||
|
kernel.attachFrame(mainFrame);
|
||||||
|
|
||||||
|
AppLauncher launcher = new AppLauncher(kernel);
|
||||||
|
registerSystemApps(launcher);
|
||||||
|
|
||||||
|
JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, launcher, desktopPane);
|
||||||
|
split.setDividerSize(0);
|
||||||
|
split.setEnabled(false);
|
||||||
|
|
||||||
|
mainFrame.setContentPane(split);
|
||||||
|
mainFrame.setVisible(true);
|
||||||
|
|
||||||
|
return mainFrame;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static synchronized JFrame getMainFrame() {
|
||||||
|
return mainFrame;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static synchronized JDesktopPane getDesktopPane() {
|
||||||
|
return desktopPane;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void registerSystemApps(AppLauncher launcher) {
|
||||||
|
// productivity
|
||||||
|
launcher.addApp(new Notes(), "Productivity");
|
||||||
|
launcher.addApp(new TabbedBrowser(), "Productivity");
|
||||||
|
launcher.addApp(new CodeDesk(), "Productivity");
|
||||||
|
launcher.addApp(new Calculator(), "Productivity");
|
||||||
|
// entertainment
|
||||||
|
launcher.addApp(new Xkcd(), "Entertainment");
|
||||||
|
// System apps
|
||||||
|
launcher.addApp(new Exit(), "System");
|
||||||
|
launcher.addApp(new TaskManager(), "System");
|
||||||
|
launcher.addApp(new FileExplorerApp(), "System");
|
||||||
|
launcher.addApp(new KbChanger(), "System");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -64,6 +64,14 @@ public class Kernel {
|
|||||||
this.desktopFrame = frame;
|
this.desktopFrame = frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void logoff() {
|
||||||
|
if (running) SwingUtilities.invokeLater(() -> {
|
||||||
|
desktopFrame.setVisible(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public void shutdown() {
|
public void shutdown() {
|
||||||
if (!running) return;
|
if (!running) return;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user