Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package gg.xp.xivsupport.gui.overlay;

public interface ActiveWindowTitleGetter {
String FFXIV_WINDOW_TITLE = "FINAL FANTASY XIV";
String getActiveWindowTitle();

boolean isFfxivActive();

}
130 changes: 67 additions & 63 deletions xivsupport/src/main/java/gg/xp/xivsupport/gui/overlay/OverlayMain.java
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
package gg.xp.xivsupport.gui.overlay;

import com.sun.jna.platform.win32.User32;
import com.sun.jna.platform.win32.WinDef;
import gg.xp.reevent.events.EventContext;
import gg.xp.reevent.events.EventMaster;
import gg.xp.reevent.events.InitEvent;
import gg.xp.reevent.scan.HandleEvents;
import gg.xp.xivsupport.events.actlines.events.OnlineStatus;
import gg.xp.xivsupport.events.debug.DebugCommand;
import gg.xp.xivsupport.events.state.PrimaryPlayerOnlineStatusChangedEvent;
import gg.xp.xivsupport.persistence.PersistenceProvider;
import gg.xp.xivsupport.persistence.Platform;
import gg.xp.xivsupport.persistence.settings.BooleanSetting;
import org.picocontainer.MutablePicoContainer;
import org.picocontainer.PicoContainer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -30,43 +28,6 @@ public final class OverlayMain {
private final EventMaster master;


@HandleEvents
public void commands(EventContext context, DebugCommand dbg) {
String command = dbg.getCommand();
switch (command) {
case "overlay:lock":
setEditing(false);
break;
case "overlay:edit":
setEditing(true);
break;
case "overlay:hide":
show.set(false);
break;
case "overlay:show":
show.set(true);
break;
case "overlay:windowinfo":
doWindowInfo();
break;
case "overlay:resetall":
// TODO:
break;
case "overlay:setallopacity":
if (dbg.getArgs().size() != 2) {
log.error("Wrong number of arguments, expected 2 ({})", dbg.getArgs());
}
setOpacity(Float.parseFloat(dbg.getArgs().get(1)));
break;
case "overlay:setallscale":
if (dbg.getArgs().size() != 2) {
log.error("Wrong number of arguments, expected 2 ({})", dbg.getArgs());
}
setScale(Float.parseFloat(dbg.getArgs().get(1)));
break;
}
}

@HandleEvents
public void handlePlayerStatusChanged(EventContext context, PrimaryPlayerOnlineStatusChangedEvent event) {
OnlineStatus status = event.getPlayerOnlineStatus();
Expand All @@ -78,16 +39,25 @@ public void handlePlayerStatusChanged(EventContext context, PrimaryPlayerOnlineS
private boolean editing;
private boolean cutscene;
// TODO: Linux support
private final boolean isNonWindows;
private final ActiveWindowTitleGetter winUtil;

public OverlayMain(PicoContainer container, OverlayConfig config, EventMaster master) {
public OverlayMain(MutablePicoContainer container, OverlayConfig config, EventMaster master) {
this.master = master;
if (!Platform.isWindows()) {
log.warn("Not running on Windows - disabling overlay support");
isNonWindows = true;
winUtil = new DummyWindowGetter();
}
else {
isNonWindows = false;
ActiveWindowTitleGetter winUtil;
try {
winUtil = new WindowsUtils();
}
catch (Throwable t) {
log.error("Error initializing WindowsUtils", t);
winUtil = new DummyWindowGetter();
}
container.addComponent(winUtil);
this.winUtil = winUtil;
}
show = config.getShow();
forceShow = config.getForceShow();
Expand All @@ -113,7 +83,8 @@ public void init(EventContext context, InitEvent init) {
}
}, om -> 200L).start();
try {
SwingUtilities.invokeAndWait(() -> {});
SwingUtilities.invokeAndWait(() -> {
});
}
catch (InterruptedException | InvocationTargetException e) {
//
Expand All @@ -124,11 +95,7 @@ public void init(EventContext context, InitEvent init) {
}

private boolean isGameWindowActive() {
if (isNonWindows) {
return true;
}
String window = getActiveWindowText();
return window.equalsIgnoreCase("FINAL FANTASY XIV") || this.overlays.stream().anyMatch(o -> o.getTitle().equals(window));
return winUtil.isFfxivActive();
}

public void addOverlay(XivOverlay overlay) {
Expand Down Expand Up @@ -182,20 +149,57 @@ public List<XivOverlay> getOverlays() {
return new ArrayList<>(overlays);
}

private static void doWindowInfo() {
log.info("Active window: {}", getActiveWindowText());
private void doWindowInfo() {
log.info("Active window: {}", winUtil.getActiveWindowTitle());
}

public static String getActiveWindowText() {
User32 u32 = User32.INSTANCE;
WinDef.HWND hwnd = u32.GetForegroundWindow();
int length = u32.GetWindowTextLength(hwnd);
if (length == 0) return "";
/* Use the character encoding for the default locale */
char[] chars = new char[length + 1];
u32.GetWindowText(hwnd, chars, length + 1);
String window = new String(chars).substring(0, length);
// log.info("Window title: [{}]", window);
return window;

@HandleEvents
public void commands(EventContext context, DebugCommand dbg) {
String command = dbg.getCommand();
switch (command) {
case "overlay:lock":
setEditing(false);
break;
case "overlay:edit":
setEditing(true);
break;
case "overlay:hide":
show.set(false);
break;
case "overlay:show":
show.set(true);
break;
case "overlay:windowinfo":
doWindowInfo();
break;
case "overlay:resetall":
// TODO:
break;
case "overlay:setallopacity":
if (dbg.getArgs().size() != 2) {
log.error("Wrong number of arguments, expected 2 ({})", dbg.getArgs());
}
setOpacity(Float.parseFloat(dbg.getArgs().get(1)));
break;
case "overlay:setallscale":
if (dbg.getArgs().size() != 2) {
log.error("Wrong number of arguments, expected 2 ({})", dbg.getArgs());
}
setScale(Float.parseFloat(dbg.getArgs().get(1)));
break;
}
}

private static class DummyWindowGetter implements ActiveWindowTitleGetter {
@Override
public String getActiveWindowTitle() {
return "Unknown";
}

@Override
public boolean isFfxivActive() {
return true;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ public void setClickThrough(boolean clickThrough) {
private static final long WS_EX_NOACTIVATE = 0x08000000L;

private static void setClickThrough(JFrame w, boolean clickThrough) {
// TODO: clean this up
log.trace("Click-through: {}", clickThrough);
w.setFocusableWindowState(!clickThrough);
if (!Platform.isWindows()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package gg.xp.xivsupport.gui.overlay;

import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.User32;
import com.sun.jna.platform.win32.WinDef;
import com.sun.jna.platform.win32.WinUser;
import org.jetbrains.annotations.Nullable;

import java.util.concurrent.atomic.AtomicReference;


public class WindowsUtils implements ActiveWindowTitleGetter {

private final User32 u32 = User32.INSTANCE;

@Override
public String getActiveWindowTitle() {
WinDef.HWND fgWindow = u32.GetForegroundWindow();
return getWindowTitle(fgWindow);
}

@Override
public boolean isFfxivActive() {
return FFXIV_WINDOW_TITLE.equalsIgnoreCase(getActiveWindowTitle());
}

public String getWindowTitle(WinDef.HWND window) {
int length = u32.GetWindowTextLength(window);
if (length == 0) return "";
/* Use the character encoding for the default locale */
char[] chars = new char[length + 1];
u32.GetWindowText(window, chars, length + 1);
return new String(chars).substring(0, length);
}

// TODO: move the windows-specific stuff into a new class
// https://discord.com/channels/551474815727304704/594899820976668673/1211457234802970645
/*
[3:38 PM] wexxlee: This should work? Tested it in all 3 window modes with focus, another window in focus, and
minimized and outputs right result...

using System.Diagnostics;
using System.Runtime.InteropServices;

long WS_POPUP = 0x80000000L;
long WS_CAPTION = 0x00C00000L;

[DllImport("user32.dll")]
static extern long GetWindowLongPtr(IntPtr hWnd, int nIndex);

var process = Process.GetProcessesByName("ffxiv_dx11").FirstOrDefault();
if (process != null)
{
IntPtr mainWindowHandle = process.MainWindowHandle;
long style = GetWindowLongPtr(mainWindowHandle, -16);
string windowType = "";
if ((style & WS_POPUP) != 0) {
windowType = "Borderless";
} else if ((style & WS_CAPTION) != 0) {
windowType = "Windowed";
} else
{
windowType = "Exclusive Fullscreen";
}

Console.WriteLine($"Type: {windowType}");
}
*/

public enum FfxivWindowMode {
NONE,
WINDOWED,
BORDERLESS,
EXCLUSIVE_FULLSCREEN
}

public @Nullable WinDef.HWND getFfxivWindow() {
AtomicReference<WinDef.HWND> out = new AtomicReference<>();
u32.EnumWindows((hwnd, data) -> {
int length = u32.GetWindowTextLength(hwnd);
if (length == 0) return true;
/* Use the character encoding for the default locale */
char[] chars = new char[length + 1];
u32.GetWindowText(hwnd, chars, length + 1);
String title = new String(chars).substring(0, length);
if (title.equalsIgnoreCase(FFXIV_WINDOW_TITLE)) {
out.set(hwnd);
return false;
}
return true;
}, Pointer.NULL);
return out.get();
}

@SuppressWarnings("unused")
public FfxivWindowMode getFfxivWindowMode() {
WinDef.HWND window = getFfxivWindow();
if (window == null) {
return FfxivWindowMode.NONE;
}
int style = u32.GetWindowLong(window, WinUser.GWL_STYLE);
if ((style & WinUser.WS_POPUP) != 0) {
return FfxivWindowMode.BORDERLESS;
}
else if ((style & WinUser.WS_CAPTION) != 0) {
return FfxivWindowMode.WINDOWED;
}
else {
return FfxivWindowMode.EXCLUSIVE_FULLSCREEN;
}
}
}
Loading