diff --git a/core/src/cn/harryh/arkpets/ArkPets.java b/core/src/cn/harryh/arkpets/ArkPets.java
index 30d68586..aca44f4d 100644
--- a/core/src/cn/harryh/arkpets/ArkPets.java
+++ b/core/src/cn/harryh/arkpets/ArkPets.java
@@ -10,7 +10,8 @@
 import cn.harryh.arkpets.transitions.TransitionFloat;
 import cn.harryh.arkpets.transitions.TransitionVector2;
 import cn.harryh.arkpets.tray.MemberTrayImpl;
-import cn.harryh.arkpets.utils.HWndCtrl;
+import cn.harryh.arkpets.hwnd.HWndCtrl;
+import cn.harryh.arkpets.hwnd.HWndCtrlFactory;
 import cn.harryh.arkpets.utils.Logger;
 import cn.harryh.arkpets.utils.Plane;
 import com.badlogic.gdx.ApplicationAdapter;
@@ -36,10 +37,10 @@ public class ArkPets extends ApplicationAdapter implements InputProcessor {
 	public TransitionFloat windowAlpha; // Window Opacity Easing
 	public TransitionVector2 windowPosition; // Window Position Easing
 
-	private HWndCtrl hWndMine;
-	private HWndCtrl hWndTopmost;
+	private HWndCtrl<?> hWndMine;
+	private HWndCtrl<?> hWndTopmost;
 	private LoopCtrl getHWndLoopCtrl;
-	private List<HWndCtrl> hWndList;
+	private List<? extends HWndCtrl<?>> hWndList;
 
 	private final String APP_TITLE;
 	private final MouseStatus mouseStatus = new MouseStatus();
@@ -95,8 +96,12 @@ public void create() {
 		// 5.Window style setup
 		windowAlpha = new TransitionFloat(TernaryFunction.EASE_OUT_CUBIC, easingDuration);
 		windowAlpha.reset(1f);
-		hWndMine = new HWndCtrl(null, APP_TITLE);
-		hWndMine.setWindowExStyle(HWndCtrl.WS_EX_LAYERED | (config.window_style_topmost ? HWndCtrl.WS_EX_TOPMOST : 0));
+		hWndMine = HWndCtrlFactory.find(null, APP_TITLE);
+		hWndMine.setLayered(true);
+		if(config.window_style_topmost){
+			hWndMine.setTopMost(true);
+		}
+		//hWndMine.setWindowExStyle(HWndCtrl.WS_EX_LAYERED | (config.window_style_topmost ? HWndCtrl.WS_EX_TOPMOST : 0));
 		promiseToolwindowStyle(1000);
 
 		// 6.Tray icon setup
@@ -155,6 +160,7 @@ public void resize(int x, int y) {
 	@Override
 	public void dispose() {
 		Logger.info("App", "Dispose");
+		HWndCtrlFactory.free();
 	}
 
 	/* INTERFACES */
@@ -194,10 +200,10 @@ public boolean touchDown(int screenX, int screenY, int pointer, int button) {
 				RelativeWindowPosition rwp = getRelativeWindowPositionAt(screenX, screenY);
 				if (rwp != null)
 					rwp.sendMouseEvent(switch (button) {
-						case Input.Buttons.LEFT -> HWndCtrl.WM_LBUTTONDOWN;
-						case Input.Buttons.RIGHT -> HWndCtrl.WM_RBUTTONDOWN;
-						case Input.Buttons.MIDDLE -> HWndCtrl.WM_MBUTTONDOWN;
-						default -> 0;
+						case Input.Buttons.LEFT -> HWndCtrl.MouseEvent.LBUTTONDOWN;
+						case Input.Buttons.RIGHT -> HWndCtrl.MouseEvent.RBUTTONDOWN;
+						case Input.Buttons.MIDDLE -> HWndCtrl.MouseEvent.MBUTTONDOWN;
+						default -> HWndCtrl.MouseEvent.EMPTY;
 					});
 			} else {
 				if (button == Input.Buttons.LEFT) {
@@ -253,10 +259,10 @@ public boolean touchUp(int screenX, int screenY, int pointer, int button) {
 				RelativeWindowPosition rwp = getRelativeWindowPositionAt(screenX, screenY);
 				if (rwp != null)
 					rwp.sendMouseEvent(switch (button) {
-						case Input.Buttons.LEFT -> HWndCtrl.WM_LBUTTONUP;
-						case Input.Buttons.RIGHT -> HWndCtrl.WM_RBUTTONUP;
-						case Input.Buttons.MIDDLE -> HWndCtrl.WM_MBUTTONUP;
-						default -> 0;
+						case Input.Buttons.LEFT -> HWndCtrl.MouseEvent.LBUTTONUP;
+						case Input.Buttons.RIGHT -> HWndCtrl.MouseEvent.RBUTTONUP;
+						case Input.Buttons.MIDDLE -> HWndCtrl.MouseEvent.MBUTTONUP;
+						default -> HWndCtrl.MouseEvent.EMPTY;
 					});
 			} else if (button == Input.Buttons.LEFT) {
 				// Left Click: Play the specified animation
@@ -292,7 +298,7 @@ public boolean mouseMoved(int screenX, int screenY) {
 			// Transfer mouse event
 			RelativeWindowPosition rwp = getRelativeWindowPositionAt(screenX, screenY);
 			if (rwp != null)
-				rwp.sendMouseEvent(HWndCtrl.WM_MOUSEMOVE);
+				rwp.sendMouseEvent(HWndCtrl.MouseEvent.MOUSEMOVE);
 		}
 		return false;
 	}
@@ -312,11 +318,11 @@ private void setWindowPos() {
 		if (hWndMine == null) return;
 		if (getHWndLoopCtrl.isExecutable(Gdx.graphics.getDeltaTime())) {
 			refreshMonitorInfo();
-			HWndCtrl new_hwnd_topmost = refreshWindowIndex();
+			HWndCtrl<?> new_hwnd_topmost = refreshWindowIndex();
 			hWndTopmost = new_hwnd_topmost != hWndTopmost ? new_hwnd_topmost : hWndTopmost;
 			hWndMine.setWindowTransparent(isAlwaysTransparent);
 		}
-		hWndMine.setWindowPosition(hWndTopmost, (int)windowPosition.now().x, (int)windowPosition.now().y, width, height);
+		hWndMine.setWindowPosition((HWndCtrl)hWndTopmost, (int)windowPosition.now().x, (int)windowPosition.now().y, width, height);
 	}
 
 	private RelativeWindowPosition getRelativeWindowPositionAt(int x, int y) {
@@ -324,7 +330,7 @@ private RelativeWindowPosition getRelativeWindowPositionAt(int x, int y) {
 			return null;
 		int absX = x + (int)(windowPosition.now().x);
 		int absY = y + (int)(windowPosition.now().y);
-		for (HWndCtrl hWndCtrl : hWndList) {
+		for (HWndCtrl<?> hWndCtrl : hWndList) {
 			if (coreTitleManager.getNumber(hWndCtrl) < 0)
 				if (hWndCtrl.posLeft <= absX && hWndCtrl.posRight > absX)
 					if (hWndCtrl.posTop <= absY && hWndCtrl.posBottom > absY) {
@@ -336,10 +342,10 @@ private RelativeWindowPosition getRelativeWindowPositionAt(int x, int y) {
 		return null;
 	}
 
-	private HWndCtrl refreshWindowIndex() {
-		hWndList = HWndCtrl.getWindowList(true);
-		HWndCtrl minWindow = null;
-		HashMap<Integer, HWndCtrl> line = new HashMap<>();
+	private HWndCtrl<?> refreshWindowIndex() {
+		hWndList = HWndCtrlFactory.getWindowList(true);
+		HWndCtrl<?> minWindow = null;
+		HashMap<Integer, HWndCtrl<?>> line = new HashMap<>();
 		int myPos = (int)(windowPosition.now().x + width / 2);
 		int minNum = 2048;
 		int myNum = coreTitleManager.getNumber(APP_TITLE);
@@ -349,7 +355,7 @@ private HWndCtrl refreshWindowIndex() {
 			plane.barriers.clear();
 			plane.pointCharges.clear();
 		}
-		for (HWndCtrl hWndCtrl : hWndList) {
+		for (HWndCtrl<?> hWndCtrl : hWndList) {
 			int wndNum = coreTitleManager.getNumber(hWndCtrl);
 			// Distinguish non-peer windows from peers.
 			if (wndNum == -1) {
@@ -360,7 +366,7 @@ private HWndCtrl refreshWindowIndex() {
 						for (int h = -hWndCtrl.posTop; h > -hWndCtrl.posBottom; h--) {
 							// Mark the window's y-position in the vertical line.
 							if (!line.containsKey(h))
-								line.put(h, (h == -hWndCtrl.posTop) ? hWndCtrl : HWndCtrl.EMPTY); // Record this window.
+								line.put(h, (h == -hWndCtrl.posTop) ? hWndCtrl : HWndCtrlFactory.EMPTY); // Record this window.
 						}
 					}
 				}
@@ -379,19 +385,19 @@ private HWndCtrl refreshWindowIndex() {
 		}
 		if (minWindow == null || minWindow.isEmpty()) {
 			// Set as the top window if there is no peer.
-			minWindow = new HWndCtrl(-1);
+			minWindow = HWndCtrlFactory.getTopMost();
 		}
 		if (plane != null) {
 			// Set barriers according to the vertical line.
 			for (int h = (int)plane.borderTop(); h > plane.borderBottom(); h--) {
 				if (line.containsKey(h)) {
-					HWndCtrl temp = line.get(h);
+					HWndCtrl<?> temp = line.get(h);
 					if (temp != null && temp.hWnd != null)
 						plane.setBarrier(-temp.posTop, temp.posLeft, temp.windowWidth, false);
 				}
 			}
 		}
-		return config.window_style_topmost ? minWindow : HWndCtrl.EMPTY; // Return the last peer window.
+		return config.window_style_topmost ? minWindow : HWndCtrlFactory.EMPTY; // Return the last peer window.
 	}
 
 	private ArkConfig.Monitor refreshMonitorInfo() {
@@ -419,7 +425,8 @@ private void promiseToolwindowStyle(int maxRetries) {
 			// Make sure ArkPets has been set as foreground window once
 			for (int i = 0; ; i++) {
 				if (hWndMine.isForeground()) {
-					hWndMine.setWindowExStyle(hWndMine.getWindowExStyle() | HWndCtrl.WS_EX_TOOLWINDOW);
+					hWndMine.setToolWindow(true);
+					//hWndMine.setWindowExStyle(hWndMine.getWindowExStyle() | HWndCtrl.WS_EX_TOOLWINDOW);
 					Logger.info("Window", "SetForegroundWindow succeeded");
 					isToolwindowStyle = true;
 					break;
@@ -507,12 +514,11 @@ public void updatePosition(int newX, int newY, int button) {
 	}
 
 
-	private record RelativeWindowPosition(HWndCtrl hWndCtrl, int relX, int relY) {
-		public void sendMouseEvent(int msg) {
-			if (msg == 0)
-				return;
+	private record RelativeWindowPosition(HWndCtrl<?> hWndCtrl, int relX, int relY) {
+		public void sendMouseEvent(HWndCtrl.MouseEvent msg) {
+			if(msg == HWndCtrl.MouseEvent.EMPTY) return;
 			//Logger.debug("Input", "Transfer mouse event " + msg + " to `" + hWndCtrl.windowText + "` @ " + relX + ", " + relY);
-			hWndCtrl.updated().sendMouseEvent(msg, relX, relY);
+			HWndCtrlFactory.update(hWndCtrl).sendMouseEvent(msg, relX, relY);
 		}
 	}
 }
diff --git a/core/src/cn/harryh/arkpets/Const.java b/core/src/cn/harryh/arkpets/Const.java
index 1b352b0e..3583cbf3 100644
--- a/core/src/cn/harryh/arkpets/Const.java
+++ b/core/src/cn/harryh/arkpets/Const.java
@@ -3,7 +3,7 @@
  */
 package cn.harryh.arkpets;
 
-import cn.harryh.arkpets.utils.HWndCtrl.NumberedTitleManager;
+import cn.harryh.arkpets.hwnd.HWndCtrl.NumberedTitleManager;
 import cn.harryh.arkpets.utils.Logger;
 import cn.harryh.arkpets.utils.Version;
 import javafx.util.Duration;
diff --git a/core/src/cn/harryh/arkpets/hwnd/HWndCtrl.java b/core/src/cn/harryh/arkpets/hwnd/HWndCtrl.java
new file mode 100644
index 00000000..3efdf728
--- /dev/null
+++ b/core/src/cn/harryh/arkpets/hwnd/HWndCtrl.java
@@ -0,0 +1,217 @@
+/** Copyright (c) 2022-2024, Harry Huang
+ * At GPL-3.0 License
+ */
+package cn.harryh.arkpets.hwnd;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+public abstract class HWndCtrl<T> {
+    public final T hWnd; //Platform HWND
+    public final String windowText;
+    public final int posTop;
+    public final int posBottom;
+    public final int posLeft;
+    public final int posRight;
+    public final int windowWidth;
+    public final int windowHeight;
+    public HWndCtrl(){
+        hWnd = null;
+        windowText = "";
+        posTop = 0;
+        posBottom = 0;
+        posLeft = 0;
+        posRight = 0;
+        windowWidth = 0;
+        windowHeight = 0;
+    }
+    public HWndCtrl(T hWnd) {
+        this.hWnd = hWnd;
+        windowText = getWindowText(hWnd);
+        WindowRect rect = getWindowRect(hWnd);
+        posTop = rect.top;
+        posBottom = rect.bottom;
+        posLeft = rect.left;
+        posRight = rect.right;
+        windowWidth = posRight-posLeft;
+        windowHeight = posBottom-posTop;
+    }
+
+    /** Returns window rect.
+     */
+    public abstract WindowRect getWindowRect(T hWnd);
+
+    /** Returns window title.
+     */
+    public abstract String getWindowText(T hWnd);
+
+    /** Returns true if the handle is empty.
+     */
+    public abstract boolean isEmpty();
+
+    /** Returns true if the window is a foreground window now.
+     */
+    public abstract boolean isForeground();
+
+    /** Returns true if the window is visible now.
+     */
+    public abstract boolean isVisible();
+
+    /** Gets the center X position.
+     * @return X.
+     */
+    public float getCenterX() {
+        return posLeft + windowWidth / 2f;
+    }
+
+    /** Gets the center Y position.
+     * @return Y.
+     */
+    public float getCenterY() {
+        return posTop + windowHeight / 2f;
+    }
+
+    /** Requests to close the window.
+     * @param timeout Timeout for waiting response (ms).
+     * @return true=success, false=failure.
+     */
+    public abstract boolean close(int timeout);
+
+    /** Sets the window as the foreground window.
+     */
+    public abstract void setForeground();
+
+    /** Sets the window's transparency.
+     * @param alpha Alpha value, from 0 to 1.
+     */
+    public abstract void setWindowAlpha(float alpha);
+
+    /** Sets the window's position without activating the window.
+     * @param insertAfter The window to precede the positioned window in the Z order.
+     * @param x The new position of the left side of the window, in client coordinates.
+     * @param y The new position of the top of the window, in client coordinates.
+     * @param w The new width of the window, in pixels.
+     * @param h The new height of the window, in pixels.
+     */
+    public abstract void setWindowPosition(HWndCtrl<T> insertAfter, int x, int y, int w, int h);
+
+    /** Sets the window's ability to be passed through.
+     * @param transparent Whether the window can be passed through.
+     */
+    public abstract void setWindowTransparent(boolean transparent);
+
+    /**
+     * Sets the window is a tool window.
+     * @param enable Whether the window is a tool window.
+     */
+    public abstract void setToolWindow(boolean enable);
+
+    /**
+     * Sets the window is layered.
+     * @param enable Whether the window is layered.
+     */
+    public abstract void setLayered(boolean enable);
+
+    /**
+     * Sets the window is topmost.
+     * @param enable Whether the window is topmost.
+     */
+    public abstract void setTopMost(boolean enable);
+
+    /** Sends a mouse event message to the window.
+     * @param msg The window message value.
+     * @param x The X-axis coordinate, related to the left border of the window.
+     * @param y The Y-axis coordinate, related to the top border of the window.
+     */
+    public abstract void sendMouseEvent(MouseEvent msg, int x, int y);
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        HWndCtrl hWndCtrl = (HWndCtrl)o;
+        return hWnd.equals(hWndCtrl.hWnd);
+    }
+
+    @Override
+    public int hashCode() {
+        return hWnd.hashCode();
+    }
+
+    @Override
+    public String toString() {
+        return "‘" + windowText + "’ " + windowWidth + "*" + windowHeight;
+    }
+
+
+    public static class NumberedTitleManager {
+        private final String zeroNameFormat;
+        private final String numberedNameFormat;
+        private final Pattern zeroNamePattern;
+        private final Pattern numberedNamePattern;
+
+        public NumberedTitleManager(String coreName) {
+            zeroNameFormat = coreName;
+            numberedNameFormat = coreName + " (%d)";
+            zeroNamePattern = Pattern.compile("^" + coreName + "$");
+            numberedNamePattern = Pattern.compile("^" + coreName + " \\(([0-9]+)\\)");
+        }
+
+        public int getNumber(HWndCtrl<?> hWndCtrl) {
+            if (hWndCtrl.isEmpty()) return -1;
+            return getNumber(hWndCtrl.windowText);
+        }
+
+        public int getNumber(String windowText) {
+            if (windowText.isEmpty()) return -1;
+            if (zeroNamePattern.matcher(windowText).find()) return 0;
+            try {
+                Matcher matcher = numberedNamePattern.matcher(windowText);
+                return matcher.find() ? Integer.parseInt(matcher.group(1)) : -1;
+            } catch (NumberFormatException ignored) {
+                return -1;
+            }
+        }
+
+        public String getIdleTitle() {
+            String title = String.format(zeroNameFormat);
+            if (HWndCtrlFactory.find(null, title) == null) {
+                return title;
+            } else {
+                for (int cur = 2; cur <= 1024 ; cur ++) {
+                    title = String.format(numberedNameFormat, cur);
+                    if (HWndCtrlFactory.find(null, title) == null)
+                        return title;
+                }
+                throw new IllegalStateException("Failed to get idle title.");
+            }
+        }
+    }
+
+    public static class WindowRect{
+        public int top;
+        public int bottom;
+        public int right;
+        public int left;
+        public WindowRect(){}
+        public WindowRect(int x,int y,int h,int w){
+            this.top=y;
+            this.left=x;
+            this.bottom=y+h;
+            this.right=x+w;
+        }
+    }
+
+    public enum MouseEvent{
+        EMPTY,
+        MOUSEMOVE,
+        LBUTTONDOWN,
+        LBUTTONUP,
+        RBUTTONDOWN,
+        RBUTTONUP,
+        MBUTTONDOWN,
+        MBUTTONUP,
+    }
+}
diff --git a/core/src/cn/harryh/arkpets/hwnd/HWndCtrlFactory.java b/core/src/cn/harryh/arkpets/hwnd/HWndCtrlFactory.java
new file mode 100644
index 00000000..01393316
--- /dev/null
+++ b/core/src/cn/harryh/arkpets/hwnd/HWndCtrlFactory.java
@@ -0,0 +1,143 @@
+package cn.harryh.arkpets.hwnd;
+
+import cn.harryh.arkpets.utils.Logger;
+import com.sun.jna.Platform;
+import com.sun.jna.platform.win32.WinDef;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+public class HWndCtrlFactory {
+    private static WindowSystem platform;
+    public static HWndCtrl<?> EMPTY;
+    public enum WindowSystem {
+        AUTO,
+        USER32,
+        X11,
+        MUTTER,
+        KWIN,
+        QUARTZ,
+        NULL
+    }
+    public static WindowSystem detectWindowSystem(){
+        if(Platform.isWindows()){
+            return WindowSystem.USER32;
+        } else if (Platform.isMac()) {
+            return WindowSystem.QUARTZ;
+        } else if (Platform.isLinux()) {
+            String desktop=System.getenv("XDG_CURRENT_DESKTOP");
+            String type=System.getenv("XDG_SESSION_TYPE");
+            if (desktop.equals("GNOME")){
+                return WindowSystem.MUTTER;
+            } else if (desktop.equals("KDE")) {
+                return WindowSystem.KWIN;
+            } else if (type.equals("x11")) {
+                return WindowSystem.X11;
+            }
+        }
+        return WindowSystem.NULL;
+    }
+
+    /**
+     * Init window system.
+     */
+    public static void init(){
+        platform=detectWindowSystem();
+        Logger.info("System","Using "+platform.toString()+" Window System");
+        //establish connection
+        switch (platform){
+            case MUTTER -> {
+                //todo
+            }
+        }
+        EMPTY=create();
+    }
+    /**
+     * Find a window.
+     * @param className window's class name.
+     * @param windowName window's title.
+     * @return HWndCtrl
+     */
+    public static HWndCtrl<?> find(String className, String windowName){
+        switch (platform){
+            case USER32 -> {
+                return User32HWndCtrl.find(className,windowName);
+            }
+            default -> {
+                return new NullHWndCtrl();
+            }
+        }
+    }
+
+    /**
+     * Create empty HWndCtrl.
+     * @return empty HWndCtrl
+     */
+    public static HWndCtrl<?> create(){
+        switch (platform){
+            case USER32 -> {
+                return new User32HWndCtrl();
+            }
+            default -> {
+                return new NullHWndCtrl();
+            }
+        }
+    }
+    /** Gets the current list of windows.
+     * @param only_visible Whether exclude the invisible window.
+     * @return An ArrayList consists of HWndCtrls.
+     */
+    public static List<? extends HWndCtrl<?>> getWindowList(boolean only_visible){
+        switch (platform){
+            case USER32 -> {
+                return User32HWndCtrl.getWindowList(only_visible);
+            }
+            default-> {
+                return new ArrayList<>();
+            }
+        }
+    }
+
+    /**
+     * Gets the topmost window.
+     * @return The topmost window's HWndCtrl.
+     */
+    public static HWndCtrl<?> getTopMost(){
+        switch (platform){
+            case USER32 -> {
+                return User32HWndCtrl.getTopMost();
+            }
+            default-> {
+                return new NullHWndCtrl();
+            }
+        }
+    }
+
+    /** Gets a new HWndCtrl which contains the updated information of this window.
+     * @return The up-to-dated HWndCtrl.
+     */
+    public static HWndCtrl<?> update(HWndCtrl<?> old){
+        switch (platform){
+            case USER32 -> {
+                return new User32HWndCtrl((WinDef.HWND) old.hWnd);
+            }
+            default-> {
+                return new NullHWndCtrl();
+            }
+        }
+    }
+
+    /** Free all resources.
+     */
+    public static void free(){
+        switch (platform){
+            case X11 -> {
+                //todo
+            }
+            default-> {
+                return;
+            }
+        }
+    }
+}
diff --git a/core/src/cn/harryh/arkpets/hwnd/NullHWndCtrl.java b/core/src/cn/harryh/arkpets/hwnd/NullHWndCtrl.java
new file mode 100644
index 00000000..66c07d9c
--- /dev/null
+++ b/core/src/cn/harryh/arkpets/hwnd/NullHWndCtrl.java
@@ -0,0 +1,73 @@
+package cn.harryh.arkpets.hwnd;
+
+public class NullHWndCtrl extends HWndCtrl<Object>{
+    @Override
+    public WindowRect getWindowRect(Object hWnd) {
+        return new WindowRect(0,0,0,0);
+    }
+
+    @Override
+    public String getWindowText(Object hWnd) {
+        return "";
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return false;
+    }
+
+    @Override
+    public boolean isForeground() {
+        return false;
+    }
+
+    @Override
+    public boolean isVisible() {
+        return true;
+    }
+
+    @Override
+    public boolean close(int timeout) {
+        return true;
+    }
+
+    @Override
+    public void setForeground() {
+
+    }
+
+    @Override
+    public void setWindowAlpha(float alpha) {
+
+    }
+
+    @Override
+    public void setWindowPosition(HWndCtrl<Object> insertAfter, int x, int y, int w, int h) {
+
+    }
+
+    @Override
+    public void setWindowTransparent(boolean transparent) {
+
+    }
+
+    @Override
+    public void setToolWindow(boolean enable) {
+
+    }
+
+    @Override
+    public void setLayered(boolean enable) {
+
+    }
+
+    @Override
+    public void setTopMost(boolean enable) {
+
+    }
+
+    @Override
+    public void sendMouseEvent(MouseEvent msg, int x, int y) {
+
+    }
+}
diff --git a/core/src/cn/harryh/arkpets/utils/HWndCtrl.java b/core/src/cn/harryh/arkpets/hwnd/User32HWndCtrl.java
similarity index 59%
rename from core/src/cn/harryh/arkpets/utils/HWndCtrl.java
rename to core/src/cn/harryh/arkpets/hwnd/User32HWndCtrl.java
index 2211027e..4de58f0a 100644
--- a/core/src/cn/harryh/arkpets/utils/HWndCtrl.java
+++ b/core/src/cn/harryh/arkpets/hwnd/User32HWndCtrl.java
@@ -1,7 +1,7 @@
 /** Copyright (c) 2022-2024, Harry Huang
  * At GPL-3.0 License
  */
-package cn.harryh.arkpets.utils;
+package cn.harryh.arkpets.hwnd;
 
 import com.sun.jna.Native;
 import com.sun.jna.Pointer;
@@ -12,22 +12,9 @@
 import com.sun.jna.platform.win32.WinUser;
 
 import java.util.ArrayList;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
-
-public class HWndCtrl {
-    public final HWND hWnd;
-    public final String windowText;
+public class User32HWndCtrl extends HWndCtrl<HWND>{
     public final Pointer windowPointer;
-    public final int posTop;
-    public final int posBottom;
-    public final int posLeft;
-    public final int posRight;
-    public final int windowWidth;
-    public final int windowHeight;
-
-    public static final HWndCtrl EMPTY = new HWndCtrl();
 
     public static final int WS_EX_TOPMOST       = 0x00000008;
     public static final int WS_EX_TRANSPARENT   = 0x00000020;
@@ -50,46 +37,35 @@ public class HWndCtrl {
     /** HWnd Controller instance.
      * @param hWnd The handle of the window.
      */
-    public HWndCtrl(HWND hWnd) {
-        this.hWnd = hWnd;
-        windowText = getWindowText(hWnd);
+    public User32HWndCtrl(HWND hWnd) {
+        super(hWnd);
         windowPointer = getWindowIdx(hWnd);
-        RECT rect = getWindowRect(hWnd);
-        posTop = rect.top;
-        posBottom = rect.bottom;
-        posLeft = rect.left;
-        posRight = rect.right;
-        windowWidth = posRight-posLeft;
-        windowHeight = posBottom-posTop;
     }
 
-    /** HWnd Controller instance.
+    /** Find a window.
      * @param className The class name of the window.
      * @param windowName The title of the window.
      */
-    public HWndCtrl(String className, String windowName) {
-        this(User32.INSTANCE.FindWindow(className, windowName));
+    public static HWndCtrl<HWND> find(String className, String windowName) {
+        HWND hwnd=User32.INSTANCE.FindWindow(className, windowName);
+        if(hwnd!=null){
+            return new User32HWndCtrl(hwnd);
+        }
+        return null;
     }
 
     /** HWnd Controller instance.
      * @param pointer The pointer.
      */
-    public HWndCtrl(int pointer) {
+    public User32HWndCtrl(int pointer) {
         this(new HWND(Pointer.createConstant(pointer)));
     }
 
     /** Empty HWnd Controller instance.
      */
-    public HWndCtrl() {
-        hWnd = null;
-        windowText = "";
+    public User32HWndCtrl() {
+        super();
         windowPointer = null;
-        posTop = 0;
-        posBottom = 0;
-        posLeft = 0;
-        posRight = 0;
-        windowWidth = 0;
-        windowHeight = 0;
     }
 
     /** Returns true if the handle is empty.
@@ -108,21 +84,7 @@ public boolean isForeground() {
     /** Returns true if the window is visible now.
      */
     public boolean isVisible() {
-        return isVisible(hWnd);
-    }
-
-    /** Gets the center X position.
-     * @return X.
-     */
-    public float getCenterX() {
-        return posLeft + windowWidth / 2f;
-    }
-
-    /** Gets the center Y position.
-     * @return Y.
-     */
-    public float getCenterY() {
-        return posTop + windowHeight / 2f;
+        return visible(hWnd);
     }
 
     /** Requests to close the window.
@@ -174,7 +136,7 @@ public void setWindowExStyle(int newLong) {
      * @param w The new width of the window, in pixels.
      * @param h The new height of the window, in pixels.
      */
-    public void setWindowPosition(HWndCtrl insertAfter, int x, int y, int w, int h) {
+    public void setWindowPosition(HWndCtrl<HWND> insertAfter, int x, int y, int w, int h) {
         if (isEmpty()) return;
         User32.INSTANCE.SetWindowPos(hWnd, insertAfter.hWnd, x, y, w, h, WinUser.SWP_NOACTIVATE);
     }
@@ -185,9 +147,45 @@ public void setWindowPosition(HWndCtrl insertAfter, int x, int y, int w, int h)
     public void setWindowTransparent(boolean transparent) {
         if (isEmpty()) return;
         if (transparent)
-            setWindowExStyle(getWindowExStyle() | HWndCtrl.WS_EX_TRANSPARENT);
+            setWindowExStyle(getWindowExStyle() | User32HWndCtrl.WS_EX_TRANSPARENT);
+        else
+            setWindowExStyle(getWindowExStyle() & ~User32HWndCtrl.WS_EX_TRANSPARENT);
+    }
+
+    /**
+     * Sets the window is a tool window.
+     * @param enable Whether the window is a tool window.
+     */
+    public void setToolWindow(boolean enable) {
+        if (isEmpty()) return;
+        if (enable)
+            setWindowExStyle(getWindowExStyle() | User32HWndCtrl.WS_EX_TOOLWINDOW);
+        else
+            setWindowExStyle(getWindowExStyle() & ~User32HWndCtrl.WS_EX_TOOLWINDOW);
+    }
+
+    /**
+     * Sets the window is layered.
+     * @param enable Whether the window is layered.
+     */
+    public void setLayered(boolean enable){
+        if (isEmpty()) return;
+        if (enable)
+            setWindowExStyle(getWindowExStyle() | User32HWndCtrl.WS_EX_LAYERED);
+        else
+            setWindowExStyle(getWindowExStyle() & ~User32HWndCtrl.WS_EX_LAYERED);
+    }
+
+    /**
+     * Sets the window is topmost.
+     * @param enable Whether the window is topmost.
+     */
+    public void setTopMost(boolean enable){
+        if (isEmpty()) return;
+        if (enable)
+            setWindowExStyle(getWindowExStyle() | User32HWndCtrl.WS_EX_TOPMOST);
         else
-            setWindowExStyle(getWindowExStyle() & ~HWndCtrl.WS_EX_TRANSPARENT);
+            setWindowExStyle(getWindowExStyle() & ~User32HWndCtrl.WS_EX_TOPMOST);
     }
 
     /** Sends a mouse event message to the window.
@@ -195,33 +193,36 @@ public void setWindowTransparent(boolean transparent) {
      * @param x The X-axis coordinate, related to the left border of the window.
      * @param y The Y-axis coordinate, related to the top border of the window.
      */
-    public void sendMouseEvent(int msg, int x, int y) {
+    public void sendMouseEvent(MouseEvent msg, int x, int y) {
+        int wmsg=switch (msg){
+            case MOUSEMOVE -> WM_MOUSEMOVE;
+            case LBUTTONDOWN -> WM_LBUTTONDOWN;
+            case LBUTTONUP -> WM_LBUTTONUP;
+            case RBUTTONDOWN -> WM_RBUTTONDOWN;
+            case RBUTTONUP -> WM_RBUTTONUP;
+            case MBUTTONDOWN -> WM_MBUTTONDOWN;
+            case MBUTTONUP -> WM_MBUTTONUP;
+            default -> 0;
+        };
         int wParam = switch (msg) {
-            case WM_LBUTTONDOWN -> MK_LBUTTON;
-            case WM_RBUTTONDOWN -> MK_RBUTTON;
-            case WM_MBUTTONDOWN -> MK_MBUTTON;
+            case LBUTTONDOWN -> MK_LBUTTON;
+            case RBUTTONDOWN -> MK_RBUTTON;
+            case MBUTTONDOWN -> MK_MBUTTON;
             default -> 0;
         };
         int lParam = (y << 16) | x;
-        User32.INSTANCE.SendMessage(hWnd, msg, new WinDef.WPARAM(wParam), new WinDef.LPARAM(lParam));
-    }
-
-    /** Gets a new HWndCtrl which contains the updated information of this window.
-     * @return The up-to-dated HWndCtrl.
-     */
-    public HWndCtrl updated() {
-        return new HWndCtrl(hWnd);
+        User32.INSTANCE.SendMessage(hWnd, wmsg, new WinDef.WPARAM(wParam), new WinDef.LPARAM(lParam));
     }
 
     /** Gets the current list of windows.
      * @param only_visible Whether exclude the invisible window.
      * @return An ArrayList consists of HWndCtrls.
      */
-    public static ArrayList<HWndCtrl> getWindowList(boolean only_visible) {
-        ArrayList<HWndCtrl> windowList = new ArrayList<>();
+    public static ArrayList<User32HWndCtrl> getWindowList(boolean only_visible) {
+        ArrayList<User32HWndCtrl> windowList = new ArrayList<>();
         User32.INSTANCE.EnumWindows((hWnd, arg1) -> {
-            if (User32.INSTANCE.IsWindow(hWnd) && (!only_visible || isVisible(hWnd)))
-                windowList.add(new HWndCtrl(hWnd));
+            if (User32.INSTANCE.IsWindow(hWnd) && (!only_visible || visible(hWnd)))
+                windowList.add(new User32HWndCtrl(hWnd));
             return true;
         }, null);
         return windowList;
@@ -232,22 +233,30 @@ public static ArrayList<HWndCtrl> getWindowList(boolean only_visible) {
      * @param exclude_ws_ex Exclude the specific window-style-extra.
      * @return An ArrayList consists of HWndCtrls.
      */
-    public static ArrayList<HWndCtrl> getWindowList(boolean only_visible, long exclude_ws_ex) {
-        ArrayList<HWndCtrl> windowList = new ArrayList<>();
+    public static ArrayList<User32HWndCtrl> getWindowList(boolean only_visible, long exclude_ws_ex) {
+        ArrayList<User32HWndCtrl> windowList = new ArrayList<>();
         User32.INSTANCE.EnumWindows((hWnd, arg1) -> {
-            if (User32.INSTANCE.IsWindow(hWnd) && (!only_visible || isVisible(hWnd))
+            if (User32.INSTANCE.IsWindow(hWnd) && (!only_visible || visible(hWnd))
                     && (User32.INSTANCE.GetWindowLong(hWnd, WinUser.GWL_EXSTYLE) & exclude_ws_ex) != exclude_ws_ex)
-                windowList.add(new HWndCtrl(hWnd));
+                windowList.add(new User32HWndCtrl(hWnd));
             return true;
         }, null);
         return windowList;
     }
 
-    private static boolean isVisible(HWND hWnd) {
+    /**
+     * Gets the topmost window.
+     * @return The topmost window's HWndCtrl.
+     */
+    public static User32HWndCtrl getTopMost(){
+        return new User32HWndCtrl(-1);
+    }
+
+    private static boolean visible(HWND hWnd) {
         try {
             if (!User32.INSTANCE.IsWindowVisible(hWnd) || !User32.INSTANCE.IsWindowEnabled(hWnd))
                 return false;
-            RECT rect = getWindowRect(hWnd);
+            WindowRect rect = getRect(hWnd);
             if (rect.top == rect.bottom || rect.left == rect.right)
                 return false;
         } catch(Exception e) {
@@ -260,16 +269,25 @@ private static Pointer getWindowIdx(HWND hWnd) {
         return hWnd.getPointer();
     }
 
-    private static String getWindowText(HWND hWnd) {
+    public String getWindowText(HWND hWnd) {
         char[] text = new char[1024];
         User32.INSTANCE.GetWindowText(hWnd, text, 1024);
         return Native.toString(text);
     }
 
-    private static RECT getWindowRect(HWND hWnd) {
+    private static WindowRect getRect(HWND hWnd) {
         RECT rect = new RECT();
         User32.INSTANCE.GetWindowRect(hWnd, rect);
-        return rect;
+        WindowRect newRect=new WindowRect();
+        newRect.top= rect.top;
+        newRect.bottom= rect.bottom;
+        newRect.left= rect.left;
+        newRect.right= rect.right;
+        return newRect;
+    }
+
+    public WindowRect getWindowRect(HWND hWnd) {
+        return getRect(hWnd);
     }
 
     @Override
@@ -277,7 +295,7 @@ public boolean equals(Object o) {
         if (this == o) return true;
         if (o == null || getClass() != o.getClass()) return false;
 
-        HWndCtrl hWndCtrl = (HWndCtrl)o;
+        User32HWndCtrl hWndCtrl = (User32HWndCtrl)o;
         return hWnd.equals(hWndCtrl.hWnd);
     }
 
@@ -290,49 +308,4 @@ public int hashCode() {
     public String toString() {
         return "‘" + windowText + "’ " + windowWidth + "*" + windowHeight + " style=" + getWindowExStyle();
     }
-
-
-    public static class NumberedTitleManager {
-        private final String zeroNameFormat;
-        private final String numberedNameFormat;
-        private final Pattern zeroNamePattern;
-        private final Pattern numberedNamePattern;
-
-        public NumberedTitleManager(String coreName) {
-            zeroNameFormat = coreName;
-            numberedNameFormat = coreName + " (%d)";
-            zeroNamePattern = Pattern.compile("^" + coreName + "$");
-            numberedNamePattern = Pattern.compile("^" + coreName + " \\(([0-9]+)\\)");
-        }
-
-        public int getNumber(HWndCtrl hWndCtrl) {
-            if (hWndCtrl.isEmpty()) return -1;
-            return getNumber(hWndCtrl.windowText);
-        }
-
-        public int getNumber(String windowText) {
-            if (windowText.isEmpty()) return -1;
-            if (zeroNamePattern.matcher(windowText).find()) return 0;
-            try {
-                Matcher matcher = numberedNamePattern.matcher(windowText);
-                return matcher.find() ? Integer.parseInt(matcher.group(1)) : -1;
-            } catch (NumberFormatException ignored) {
-                return -1;
-            }
-        }
-
-        public String getIdleTitle() {
-            String title = String.format(zeroNameFormat);
-            if (User32.INSTANCE.FindWindow(null, title) == null) {
-                return title;
-            } else {
-                for (int cur = 2; cur <= 1024 ; cur ++) {
-                    title = String.format(numberedNameFormat, cur);
-                    if (User32.INSTANCE.FindWindow(null, title) == null)
-                        return title;
-                }
-                throw new IllegalStateException("Failed to get idle title.");
-            }
-        }
-    }
 }
diff --git a/desktop/src/cn/harryh/arkpets/EmbeddedLauncher.java b/desktop/src/cn/harryh/arkpets/EmbeddedLauncher.java
index dc187ee7..a1ff0178 100644
--- a/desktop/src/cn/harryh/arkpets/EmbeddedLauncher.java
+++ b/desktop/src/cn/harryh/arkpets/EmbeddedLauncher.java
@@ -4,6 +4,7 @@
 package cn.harryh.arkpets;
 
 import cn.harryh.arkpets.utils.ArgPending;
+import cn.harryh.arkpets.hwnd.HWndCtrlFactory;
 import cn.harryh.arkpets.utils.Logger;
 import com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application;
 import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration;
@@ -57,6 +58,7 @@ protected void process(String command, String addition) {
         Logger.debug("System", "Default charset is " + Charset.defaultCharset());
 
         try {
+            HWndCtrlFactory.init();
             Lwjgl3ApplicationConfiguration config = new Lwjgl3ApplicationConfiguration();
             // Configure FPS
             config.setForegroundFPS(fpsDefault);