I wrote some java code.
how to run chrome using JNA in windows(32bit).
then I like to get the hwnd of it.
As you know, FindWindow is simple solution but if chrome doesn't running, it doesn't work.
FindWindow example
below like code is possible?
HWND hwnd = User32.CreateProcess(...);
below code open chrome.
but sizing, maxmizing doesn't work.
public interface Kernel32 extends StdCallLibrary {
Kernel32 INSTANCE = (Kernel32) Native.loadLibrary("kernel32", Kernel32.class);
boolean CreateProcessA(
String lpApplicationName
, String lpCommandLine
, Structure lpProcessAttributes
, Structure lpThreadAttributes
, boolean bInheritHandles
, int dwCreationFlags
, Structure lpEnvironment
, String lpCurrentDirectory
, Structure lpStartupInfo
, Structure lpProcessInformation);
}
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
public class ProcessInformation extends Structure {
public Pointer hProcess;
public Pointer hThread;
public int dwProcessId;
public int dwThreadId;
}
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import com.sun.jna.WString;
public class StartupInfoA extends Structure {
public int cb;
public WString lpReserved;
public WString lpDesktop;
public WString lpTitle;
public int dwX;
public int dwY;
public int dwXSize;
public int dwYSize;
public int dwXCountChars;
public int dwYCountChars;
public int dwFillAttribute;
public int dwFlags;
public short wShowWindow;
public short cbReserved2;
public Pointer lpReserved2;
public Pointer hStdInput;
public Pointer hStdOutput;
public Pointer hStdError;
}
public class Test {
public static void main(String[] args) {
int STARTF_USEPOSITION = 0x00000004;
int STARTF_USESIZE = 0x00000002;
int STARTF_USESHOWWINDOW = 0x00000001;
ProcessInformation processInformation = new ProcessInformation();
StartupInfoA startupInfo = new StartupInfoA();
startupInfo.dwX = 100;
startupInfo.dwY = 100;
startupInfo.dwXSize = 100;
startupInfo.dwYSize = 100;
startupInfo.wShowWindow = (short) SW_MAXIMIZE;
startupInfo.dwFlags = STARTF_USEPOSITION | STARTF_USESIZE;
Kernel32.INSTANCE.CreateProcessA(new String("C:\\Users.....\\Google\\Chrome\\Application\\chrome.exe")
, null
, null
, null
, true
, 0
, null
, null
, startupInfo
, processInformation);
}
}
If Chrome is not running, you can't get a handle of its window, of course, because such a window does not exist. You might want to run Chrome, using something like ProcessBuilder, then call something like this:
user32.EnumWindows( new WndEnumProc()
{
#SuppressWarnings ( "AssignmentToMethodParameter" )
public boolean callback ( int hWnd, int lParam )
{
if ( user32.IsWindow( hWnd ) )
{
if ( user32.IsWindowVisible( hWnd ) )
{
RECT r = new RECT();
user32.GetWindowRect( hWnd, r );
// if (r.left > -32000) // is not minimized
//{
String windowTitle = getWindowParentName( hWnd );
String windowClass = getWindowParentClassName( hWnd );
hWnd = user32.GetAncestor( hWnd, 3 );
if ( !windowTitle.toLowerCase().equals( "program manager" ) && !windowClass.toLowerCase().equals( "progman" ) && !windowTitle.equals( "" ) && !windowClass.toLowerCase().equals( "shell_traywnd" ) )
{
listOfWindows.put( hWnd, getWindowParentRectangle( hWnd ) );
}
// }
}
return true;
}
else
{
return false;
}
}
}, 0 );
Of course, this is already working code that has some conditions specific to my app. But the main idea is to
call EnumWindows with a WndEnumProc() that will put all windows it finds to a collection ( in my case an HashMap ). Then after the EnumWindows returns, you will have a collection of windows in your listOfWindows variable and you will be able to get the hwnd of Chrome if it was running during the time EnumWindows was called.
You should define EnumWindows in your user32 instance like this:
/**
* Enumerates all top-level windows on the screen by passing the handle to each window, in turn, to an application-defined callback function.
* #param wndenumproc A pointer to an application-defined callback function.
* #param lParam An application-defined value to be passed to the callback function.
* #return if the function succeeded.
* <b>Microsoft Reference</b><br>
*/
public boolean EnumWindows ( WndEnumProc wndenumproc, int lParam );
You should also define your WndEnumProc structure in a class (mine was named Structures) something like this:
public static interface WndEnumProc extends StdCallLibrary.StdCallCallback
{
boolean callback ( int hWnd, int lParam );
}
Hope that helps. Please note that Chrome must be running while you are doing all that magic. Running it, as I noted in the beginning, should be relatively straightforward using a ProcessBuilder or if you don't want to bother much and Chrome is in your path, you can use
System.getRuntime().exec("chrome.exe")
to start Chrome.
Related
I am invoking the win32 API function GetMenuItemInfoW. When I invoke the function I it returns false, and Native.getLastError() returns 87:
ERROR_INVALID_PARAMETER 87 (0x57) The parameter is incorrect.
I think that my error is in my implementation of the structure MenuItemInfoW:
#Structure.FieldOrder({"cbSize", "fMask", "fType", "fState", "wId", "hSubMenu", "hBmpChecked", "hBmpUnchecked", "dwItemData", "dwTypeData", "cch", "hbmpItem"})
public class MENUITEMINFOW extends Structure {
public int cbSize;
public int fMask;
public int fType;
public int fState;
public int wId;
public Pointer hSubMenu;
public Pointer hBmpChecked;
public Pointer hBmpUnchecked;
public WinDef.ULONGByReference dwItemData;
public WString dwTypeData;
public int cch;
public Pointer hbmpItem;
public MENUITEMINFOW() {
super();
}
public MENUITEMINFOW(Pointer pointer) {
super(pointer);
}
}
And this is my code for invoking the function:
MENUITEMINFOW menuiteminfow = new MENUITEMINFOW();
menuiteminfow.fMask = 0x00000040 | 0x00000080 | 0x00000004 | 0x00000002;
menuiteminfow.fType = 0x00000000;
menuiteminfow.cch = 256;
menuiteminfow.dwTypeData = new WString(String.join("", Collections.nCopies(256, " ")));
menuiteminfow.cbSize = Native.getNativeSize(menuiteminfow.getClass());
WinDef.BOOL result = User32Ex.INSTANCE.GetMenuItemInfoW(hMenu.getPointer(), 0, true, menuiteminfow.getPointer());
if (!result.booleanValue()) {
int errorCode = Native.getLastError();
System.out.println("Error Code: " + errorCode);
}
I have edited my editing my code for your tip, but I get the same error 87. This is my new code:
MENUITEMINFOW menuiteminfow = new MENUITEMINFOW();
menuiteminfow.fMask = 0x00000040 | 0x00000080 | 0x00000004 | 0x00000002;
menuiteminfow.fType = 0x00000000;
menuiteminfow.cch = 0;
menuiteminfow.dwTypeData = Pointer.NULL;
menuiteminfow.cbSize = Native.getNativeSize(menuiteminfow.getClass());
WinDef.BOOL result = User32Ex.INSTANCE.GetMenuItemInfoW(hMenu.getPointer(),
new WinDef.UINT(0), new WinDef.BOOL(true), menuiteminfow.getPointer());
if (!result.booleanValue()) {
int errorCode = Native.getLastError();
System.out.println("Error Code: " + errorCode);
}
And this is new version of my structure:
#Structure.FieldOrder({"cbSize", "fMask", "fType", "fState", "wId", "hSubMenu", "hBmpChecked", "hBmpUnchecked", "dwItemData", "dwTypeData", "cch", "hbmpItem"})
public class MENUITEMINFOW extends Structure {
public int cbSize;
public int fMask;
public int fType;
public int fState;
public int wId;
public Pointer hSubMenu;
public Pointer hBmpChecked;
public Pointer hBmpUnchecked;
public BaseTSD.LONG_PTR dwItemData;
public Pointer dwTypeData;
public int cch;
public WinDef.HBITMAP hbmpItem;
public MENUITEMINFOW() {
super();
}
public MENUITEMINFOW(Pointer pointer) {
super(pointer);
this.read();
}
}
Thank you for your response.
Your structure mapping can be improved.
First, a ULONG_PTR is not the same as a WinDef.ULONGByReference. The ULONG_PTR is a pointer-sized value, but it does not actually point to anything.
JNA has a built-in BaseTSD.LONG_PTR type which you could use here.
While a Pointer technically works for hbmpItem and the other bitmap fields, the type WinDef.HBITMAP is already defined in JNA so you should use that mapping.
Finally, and the likely cause of your error, dwTypeData receives a String, but it is a buffer that you need to allocate and fill. From the MENUITEMINFO docs:
To retrieve a menu item of type MFT_STRING, first find the size of the string by setting the dwTypeData member of MENUITEMINFO to NULL and then calling GetMenuItemInfo. The value of cch+1 is the size needed. Then allocate a buffer of this size, place the pointer to the buffer in dwTypeData, increment cch, and call GetMenuItemInfo once again to fill the buffer with the string.
So in this case, you'd want to map a Pointer to dwTypeData, call the function the first time with that pointer set to Pointer.NULL, and then alloating memory for it based on incrementing cch:
menuiteminfow.cch++;
// allocate 2 bytes per widechar
menuiteminfow.dwTypeData = new Memory(cch * 2);
You can fetch the string from there after the function call with menuiteminfow.dwTypeData.getWideString().
I'm attempting to use the Chinese (Traditional, Taiwan), Chinese (Traditional) - New Phonetic keyboard on an English (US) Windows 7. When I type into a Java Swing-based text area, the candidate list is showing up on the bottom-right of my screen, regardless of where the text area is positioned on the screen. When I'm not using a Java program, the candidate list shows up in the correct place, directly under the text I'm typing.
Has anybody else run into this behavior and found a workaround for it? I haven't found other reports of this behavior online.
Thanks in advance for any help!
System Details:
Microsoft New Phonetic IME 10.1 (10.1.7601.0)
Chinese input mode
Either half or full shape (doesn't matter)
Standard keyboard layout
Windows 7, 64-bit (same happens on 32-bit)
Affects Java 6, 7, and 8
Affects Swing and JavaFX
I did eventually find similar problems reported, but most of them were related to Japanese IMEs and have already been fixed in the JDK. I didn't find any reports specific to this Chinese IME, but I did find a workaround in case it's useful for others.
The brief summary is that I listen for the WM_IME_STARTCOMPOSITION Windows message. When I see that, I locate the IME candidate window, move it to the location I want, and override its WindowProc to prevent further moves. During composition I also listen for WM_KEYDOWN events because I no longer received any WM_IME messages while the user was composing, even though the candidate window closes and gets recreated several times throughout composition. When I receive the WM_IME_ENDCOMPOSITON message, I stop listening for WM_KEYDOWN messages.
As an alternative approach, I tried sending a WM_IME_CONTROL message with the IMC_SETCANDIDATEPOS command to move the candidate window, but this particular IME seems to ignore it.
I used JNA (https://github.com/twall/jna) to override the WindowProc on both the window containing my text area as well as the IME candidate window.
The code snippet below is an example of the workaround.
hwndMain = WIN_INSTANCE.FindWindow(null, "Main Window");
// Note the existing WindowProc so we can restore it later.
prevWndProc = new BaseTSD.LONG_PTR((long) WIN_INSTANCE.GetWindowLong(hwndMain, WinUser.GWL_WNDPROC));
// Register a new WindowProc that we will use to intercept IME messages.
mainListener = new WindowsCallbackListener() {
#Override
public int callback(int hWnd, int uMsg, int uParam, int lParam) {
if (uMsg == WM_IME_STARTCOMPOSITION || (imeComposing && uMsg == WM_KEYDOWN)) {
imeComposing = true;
final WinDef.HWND hwndIme = WIN_INSTANCE.FindWindow("SYSIME7_READING_UI", null);
if (hwndIme != null && !hwndIme.equals(imeWindow)) {
// We found an IME window that is not the same as the last one. We assume the last one was
// closed. We need to register our callback with the new window.
imeWindow = hwndIme;
final Point imeWindowLocation = getImeWindowLocation();
WIN_INSTANCE.MoveWindow(hwndIme, imeWindowLocation.x, imeWindowLocation.y, 0, 0, true);
final BaseTSD.LONG_PTR prevWndProcIme =
new BaseTSD.LONG_PTR((long) WIN_INSTANCE.GetWindowLong(hwndIme, WinUser.GWL_WNDPROC));
imeListener = new WindowsCallbackListener() {
#Override
public int callback(int hWnd, int uMsg, int uParam, int lParam) {
if (uMsg == WM_WINDOWPOSCHANGING) {
final WindowPosition pos = new WindowPosition(new Pointer((long)lParam));
pos.read();
pos.flags |= SWP_NOMOVE;
pos.write();
}
// Call the window's actual WndProc so the events get processed.
return WIN_INSTANCE.CallWindowProc(prevWndProcIme, hWnd, uMsg, uParam, lParam);
}
};
// Set the WndProc function to use our callback listener instead of the window's one.
WIN_INSTANCE.SetWindowLong(hwndIme, WinUser.GWL_WNDPROC, imeListener);
}
}
else if (uMsg == WM_IME_ENDCOMPOSITION) {
// We can discard the IME listener since its window is closed. If another one gets opened, we'll
// create a new listener.
imeListener = null;
imeComposing = false;
}
// Call the window's previous WindowProc so the event continues to get processed.
return WIN_INSTANCE.CallWindowProc(prevWndProc, hWnd, uMsg, uParam, lParam);
}
};
// Set the WindowProc function to use our WindowProc so the event continues to get processed.
WIN_INSTANCE.SetWindowLong(hwndMain, WinUser.GWL_WNDPROC, mainListener);
The code above assumes the following definitions:
private static final MyUser32 WIN_INSTANCE = MyUser32.INSTANCE;
private static final int SWP_NOMOVE = 2;
private static final int WM_KEYDOWN = 256;
private static final int WM_WINDOWPOSCHANGING = 70;
private static final int WM_IME_ENDCOMPOSITION = 270;
private static final int WM_IME_STARTCOMPOSITION = 269;
private WinDef.HWND hwndMain;
private BaseTSD.LONG_PTR prevWndProc;
// Keep references to these listeners so they don't get garbage-collected.
private WindowsCallbackListener mainListener;
private WindowsCallbackListener imeListener;
private boolean imeComposing;
private WinDef.HWND imeWindow;
public static class WindowPosition extends Structure {
public WinDef.HWND hwnd;
public WinDef.HWND hwndInsertAfter;
public int x;
public int y;
public int cx;
public int cy;
public int flags;
public WindowPosition(Pointer p) {
super(p);
}
#Override
protected List getFieldOrder() {
return Arrays.asList("hwnd", "hwndInsertAfter", "x", "y", "cx", "cy", "flags");
}
}
private interface MyUser32 extends User32 {
MyUser32 INSTANCE = (MyUser32) Native.loadLibrary("user32", MyUser32.class, W32APIOptions.DEFAULT_OPTIONS);
int CallWindowProc(BaseTSD.LONG_PTR prevWndProc, int hWnd, int uMsg, int uParam, int lParam);
int SetWindowLong(HWND hwnd, int nIndex, BaseTSD.LONG_PTR listener) throws LastErrorException;
int SetWindowLong(HWND hwnd, int nIndex, WindowsCallbackListener listener) throws LastErrorException;
}
private interface WindowsCallbackListener extends Callback, StdCall {
int callback(int hWnd, int Msg, int wParam, int lParam);
}
Is it possible to map The following Macro function with JNA?
int ListView_FindItem(
HWND hwnd,
int iStart,
const LPLVFINDINFO plvfi
);
I've tried to map this function with StdCallLibraryb, but that does not seem to work
(Function not found exception is thrown).
Basically i am trying find the index of a particular item in List view of desktop.
I have the name of the item i intend to find.
EDIT:
i have tried using the send message feature, i get the following exception
Exception in thread "main" java.lang.UnsatisfiedLinkError: Error looking up function
'GetMessage': The specified procedure could not be found.
at com.sun.jna.Function.<init>(Function.java:179)
at com.sun.jna.NativeLibrary.getFunction(NativeLibrary.java:347)
at com.sun.jna.NativeLibrary.getFunction(NativeLibrary.java:327)
at com.sun.jna.Library$Handler.invoke(Library.java:203)
at $Proxy0.GetMessage(Unknown Source)
at javaapplication4.Main.main(Main.java:43)
Java Result: 1
This is the code i used
public class Main {
public static class LVFINDINFO extends Structure {
public int flags =1002;
public String psz = "new folder3";
public LPARAM lParam ;
public POINT pt;
public int vkDirection;
}
public static class MSG extends Structure {
public HWND hWnd;
public int message;
public int wParam =-1;
public LVFINDINFO lParam1;
public int time;
public POINT pt;
public MSG(LVFINDINFO lParam) {
lParam1 = lParam;
}
}
public static void main(String[] args) {
User32 user32 = (User32) Native.loadLibrary("User32", User32.class);
LVFINDINFO i = new LVFINDINFO();
MSG m = new MSG(i);
user32.GetMessage(m, user32.GetDesktopWindow(), 0, 0);
System.out.println(user32.GetMessage(m, user32.GetDesktopWindow(), 0, 0));
}
}
Since a macro exists purely at compile-time, there's no way to call it using JNA.
You'll need to see what the macro actually does and do that instead. According to the documentation it sends the LVM_FINDITEM message. You need to find out how to send that message using JNA.
I'm trying to detect the location of AppData\LocalLow work on Java with JNA on Windows 7. But the closest function available for the job is:
W32API.HRESULT SHGetFolderPath(W32API.HWND hwndOwner,int nFolder,W32API.HANDLE
hToken,W32API.DWORD dwFlags,char[] pszPath)
Here we have the Solution in C#
But in my case, JAVA + JNA, I'm wondering how I can use the LocalLow GUID with SHGetFolderPath only, or maybe I should look at the problem from a different angle (maybe JNI would be better here?)
If somebody can help on that, thanks
Cheers
EDIT:
Ok, now I added SHGetKnownFolderPath, but here, it keeps returning me strings like that "?f"
static interface Shell32 extends Library {
public static final int MAX_PATH = 260;
public static final String FOLDERID_APPDATALOW = "{A520A1A4-1780-4FF6-BD18-167343C5AF16}";
static Shell32 INSTANCE = (Shell32) Native.loadLibrary("shell32",
Shell32.class, OPTIONS);
public int SHGetKnownFolderPath(Guid.GUID rfid, int dwFlags, HANDLE hToken,
char[] pszPath);
}
public static void main(String[] args) {
char[] pszPath = new char[Shell32.MAX_PATH];
Guid.GUID localLowId = Ole32Util.getGUIDFromString(Shell32.FOLDERID_APPDATALOW);
int hResult = Shell32.INSTANCE.SHGetKnownFolderPath(localLowId, 0, null, pszPath);
if (hResult == 0) {
String path = new String(pszPath);
int len = path.indexOf('\0');
path = path.substring(0, len);
System.out.println(path);
} else {
System.err.println("Error: " + hResult);
}
}
You can extend Shell32 (or create your own similar class) to get access to the SHGetKnownFolderPath API:
W32API.HRESULT SHGetKnownFolderPath(
Guid.GUID rfid,
W32API.DWORD dwFlags,
W32API.HANDLE hToken,
char[] pszPath);
This Question is Somewhat old However I managed to Solve this issue.
allow me to explain why Kyro approach does not work quite well. This is due
SHGetKnownFolderPath uses a POINTER and not a String or A character array (that why show odd output) . therefore you need to use something like this:
public int SHGetKnownFolderPath(Guid.GUID rfid, int dwFlags, HANDLE hToken, PointerByReference pszPath);
a pointer by reference...
so on this case i attach a example I used:
static interface Shell32 extends StdCallLibrary {
public static final String FOLDERID_APPDATALOW = "{A520A1A4-1780-4FF6-BD18-167343C5AF16}";
static Shell32 INSTANCE = (Shell32) Native.loadLibrary("shell32",
Shell32.class, W32APIOptions.UNICODE_OPTIONS);
public int SHGetKnownFolderPath(Guid.GUID rfid, int dwFlags, HANDLE hToken,
PointerByReference pszPath);
}
public static void main(String[] args) {
Guid.GUID localLowId = Ole32Util.getGUIDFromString(Shell32.FOLDERID_APPDATALOW);
PointerByReference e= new PointerByReference();
int hResult = Shell32.INSTANCE.SHGetKnownFolderPath(localLowId, 0, null, e);
if (hResult == 0) {
char delim='\0';
char Array[]=e.getValue().getCharArray(0,255);
for (int i = 0; i < Array.length; i++) {
if(Array[i]==delim){
char temparr[]=new char[i];
System.arraycopy(Array,0,temparr,0,i);
Array=temparr;
break;
}
}
/*dont forget to release the Pointer*/
Ole32.INSTANCE.CoTaskMemFree(e.getValue());
System.out.println(Array);
} else {
System.err.println("Error: " + hResult);
}
}
private static interface Ole32 extends StdCallLibrary {
Ole32 INSTANCE = (Ole32) Native.loadLibrary(
"Ole32", Ole32.class, W32APIOptions.UNICODE_OPTIONS);
void CoTaskMemFree(Pointer pv);
}
Is there a way I can use Java (or Groovy) to change my desktop wallpaper in Windows XP? I have a program that creates a new image every day (or whenever) and I would like a way to automatically update my desktop.
I've seem some questions on this site about C++ or .NET, but I did not see anything specific to Java.
Sorry I'm a bit behind #ataylor's answer because I was preparing a snippet to do it. Yes, JNA is a correct approach. Here you go:
import java.util.HashMap;
import com.sun.jna.Native;
import com.sun.jna.platform.win32.WinDef.UINT_PTR;
import com.sun.jna.win32.*;
public class WallpaperChanger {
public static void main(String[] args) {
//supply your own path instead of using this one
String path = "C:\\Users\\Public\\Pictures\\Sample Pictures\\Chrysanthemum.jpg";
SPI.INSTANCE.SystemParametersInfo(
new UINT_PTR(SPI.SPI_SETDESKWALLPAPER),
new UINT_PTR(0),
path,
new UINT_PTR(SPI.SPIF_UPDATEINIFILE | SPI.SPIF_SENDWININICHANGE));
}
public interface SPI extends StdCallLibrary {
//from MSDN article
long SPI_SETDESKWALLPAPER = 20;
long SPIF_UPDATEINIFILE = 0x01;
long SPIF_SENDWININICHANGE = 0x02;
SPI INSTANCE = (SPI) Native.loadLibrary("user32", SPI.class, new HashMap<Object, Object>() {
{
put(OPTION_TYPE_MAPPER, W32APITypeMapper.UNICODE);
put(OPTION_FUNCTION_MAPPER, W32APIFunctionMapper.UNICODE);
}
});
boolean SystemParametersInfo(
UINT_PTR uiAction,
UINT_PTR uiParam,
String pvParam,
UINT_PTR fWinIni
);
}
}
You need to have the JNA libraries on the classpath for this to work. This was tested in Windows 7, there might be some nuances in XP but I think it should work. That API is presumably stable.
References
Setting Wallpaper - Coding4Fun
How to determine if a screensaver is running in Java?
W32API.java
Edit (2010/01/20):
I had previously omitted the options SPIF_UPDATEINIFILE and SPIF_SENDWININICHANGE. These are now being used as they were suggested in the Coding4Fun MSDN article.
You can do it easier:
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.platform.win32.WinDef.HWND;
import com.sun.jna.platform.win32.WinDef.PVOID;
import com.sun.jna.win32.W32APIOptions;
public class Wallpaper {
public static interface User32 extends Library {
User32 INSTANCE = (User32) Native.loadLibrary("user32",User32.class,W32APIOptions.DEFAULT_OPTIONS);
boolean SystemParametersInfo (int one, int two, String s ,int three);
}
public static void main(String[] args) {
User32.INSTANCE.SystemParametersInfo(0x0014, 0, "C:\\Users\\Public\\Pictures\\Sample Pictures\\Chrysanthemum.jpg" , 1);
}
}
You can write a batch file to change the wall-paper, and execute that batch file using,
Runtime.getRuntime.exec()
The JNA java library allows you to easily call Win32 API calls. In particular, to change the desktop background, you need to call the SystemParametersInfo function.
Take a look at this article for an introduction to JNA: http://today.java.net/article/2009/11/11/simplify-native-code-access-jna
Here is a Pure Java implementation which uses Project Panama to make the native callbacks into Windows USER32.DLL. Note that the API is incubating so has changed between JDK16, 17 and later builds. These samples use the versions of Panama that are in the current JDK16/17 release, some changes may be required if you switch to the latest Panama Early Access builds.
import java.lang.invoke.*;
import java.nio.file.Path;
import jdk.incubator.foreign.*;
/**
%JDK16%\bin\java -Dforeign.restricted=permit --add-modules jdk.incubator.foreign SetWallpaper.java A.JPG
*/
public class SetWallpaper {
static final int SPI_SETDESKWALLPAPER = 0x0014;
static final int SPIF_UPDATEINIFILE = 0x01;
static final int SPIF_SENDCHANGE = 0x02;
public static void main(String[] args) throws Throwable {
LibraryLookup user32 = LibraryLookup.ofLibrary("user32");
MethodHandle spi = CLinker.getInstance().downcallHandle(user32.lookup("SystemParametersInfoA").get()
// BOOL SystemParametersInfoA (UINT uiAction, UINT uiParam, PVOID pvParam, UINT fWinIni);
, MethodType.methodType(int.class, int.class, int.class, MemoryAddress.class, int.class)
, FunctionDescriptor.of(CLinker.C_LONG,CLinker.C_LONG, CLinker.C_LONG, CLinker.C_POINTER, CLinker.C_LONG));
Path path = Path.of(args[0]).toRealPath();
try (NativeScope scope = NativeScope.unboundedScope()) {
MemorySegment img = CLinker.toCString(path.toString(), scope);
int status = (int)spi.invokeExact(SPI_SETDESKWALLPAPER, 0, img.address(), SPIF_UPDATEINIFILE | SPIF_SENDCHANGE);
System.out.println("Changed wallpaper to "+path+" rc="+status+(status == 0 ? " *** ERROR ***": " OK"));
}
}
}
Small changes needed for JDK17:
/**
%JAVA_HOME%\bin\java --enable-native-access=ALL-UNNAMED --add-modules jdk.incubator.foreign SetWallpaper.java A.JPG
*/
public static void main(String[] args) throws Throwable {
System.loadLibrary("user32");
// BOOL SystemParametersInfoA(UINT uiAction, UINT uiParam, PVOID pvParam, UINT fWinIni);
MemoryAddress symbol = SymbolLookup.loaderLookup().lookup("SystemParametersInfoW").get();
MethodHandle SystemParametersInfoW = CLinker.getInstance().downcallHandle(symbol
, MethodType.methodType(int.class, int.class, int.class, MemoryAddress.class, int.class)
, FunctionDescriptor.of(CLinker.C_LONG,CLinker.C_LONG, CLinker.C_LONG, CLinker.C_POINTER, CLinker.C_LONG));
Path path = Path.of(args[0]).toRealPath();
try(ResourceScope scope = ResourceScope.newConfinedScope()) {
SegmentAllocator allocator = SegmentAllocator.arenaAllocator(scope);
// toCString as WIDE string
Addressable wide = allocator.allocateArray(CLinker.C_CHAR, (path+"\0").getBytes(StandardCharsets.UTF_16LE));
int status = (int)SystemParametersInfoW.invokeExact(SPI_SETDESKWALLPAPER, 0, wide.address(), SPIF_UPDATEINIFILE | SPIF_SENDCHANGE);
System.out.println("Changed wallpaper to "+path+" rc="+status+(status == 0 ? " *** ERROR ***": " OK"));
}
}
Expanding on the answer from #DuncG, here's an updated solution which uses Project Panama from JDK 18.
import jdk.incubator.foreign.*;
import java.lang.invoke.MethodHandle;
import java.nio.file.Path;
import static jdk.incubator.foreign.ValueLayout.*;
public class WindowsOperatingSystem {
private static final int SPI_SETDESKWALLPAPER = 0x0014;
private static final int SPIF_UPDATEINIFILE = 0x01;
private static final int SPIF_SENDCHANGE = 0x02;
private static final MethodHandle systemParametersInfoAFunction;
static {
System.loadLibrary("user32");
// BOOL SystemParametersInfoA(UINT uiAction, UINT uiParam, PVOID pvParam, UINT fWinIni);
systemParametersInfoAFunction = CLinker.systemCLinker().downcallHandle(
SymbolLookup.loaderLookup().lookup("SystemParametersInfoA").get(),
FunctionDescriptor.of(JAVA_BOOLEAN, JAVA_INT, JAVA_INT, ADDRESS, JAVA_INT)
);
}
public static void setWallpaper(Path file) {
try (ResourceScope scope = ResourceScope.newConfinedScope()) {
SegmentAllocator allocator = SegmentAllocator.nativeAllocator(scope);
Addressable nativeFilePath = allocator.allocateUtf8String(file.toString());
var result = (boolean)systemParametersInfoAFunction.invokeExact(
SPI_SETDESKWALLPAPER,
0,
nativeFilePath,
SPIF_UPDATEINIFILE | SPIF_SENDCHANGE
);
if (!result) {
throw new IllegalStateException();
}
} catch (Error | RuntimeException t) {
throw t;
} catch (Throwable t) {
throw new RuntimeException(t);
}
}
}