Zrobiłem sobie kiedyś pośrednika przekazującego z JVM zdarzenia zmiany rozdzielczości domyślnego ekranu. Działało to mniej więcej nieźle, tylko z konieczności używało nieładnych importów takich jak sun.java2d.SunGraphicsEnvironment czy sun.awt.DisplayChangedListener. Żeby się trochę zabezpieczyć przed kolejnymi wersjami Javy nie używałem tego bezpośrednio, ale nieco obudowałem, żeby w razie czego musieć zmieniać jak najmniej w tym pośredniku i nic w kodzie, który z niego korzystał.
Ostatnio zachciało mi się wyeliminować nawet te importy, więc zabrałem się za przerobienie na wersję bardziej refleksyjną ;)
Zrobiło się to nieco bardziej skomplikowane niż banalne bo trzeba było zarejestrować listenera na obiekcie GraphicsEnvironment, który oficjalnie tego nie robi, przy pomocy klasy implementującej interfejs, którego oficjalnie nie ma.
O ile wyciągnięcie z obiektu SunGraphicsEnvironment aka GraphicsEnvironment właściwych metod to żaden problem, potknąłem się chyba na wytworzeniu klasy implementującej interfejs sun.awt.DisplayChangedListener. W teorii wiem jak to robić i kiedyś coś nawet robiłem, ale nigdy nie potrzebowałem wyciągnąć class loadera, do interfejsu, z którego mogłem użyć tylko stringa jako nazwy.
Żeby nie było na sucho, poniżej cały kod pośrednika i na końcu dodatkowa klasa okienka z przykładem użycia. Pośrednik, czyli "DisplayListener.To" zawiera zakomentowany wciąż działający kod nie używający refleksji, którego można sobie przełączyć (odkomentować i zakomentować wersje refleksyjne), żeby mieć działające powiadomienia o zmianach grafiki domyślnego ekranu systemu. Zgrzyt w tamtej wersji jest tylko taki kompilator krzyczy, że używa się prywatnych interfejsów Suna, ale poza tym wszystko działa.
Jednak bardziej mnie interesuje wersja z refleksją, która powinna być w przyszłości bardziej "elastyczna".
Oto kod:
//#define REFLECTION
package com.olamagato.video;
import com.olamagato.video.DisplayListener.To;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.awt.SystemColor;
import java.awt.Toolkit;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.text.DateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
//#ifdef REFLECTION
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import javax.swing.SwingUtilities;
//#elif
//import sun.awt.DisplayChangedListener;
//import sun.java2d.SunGraphicsEnvironment;
//#endif
/**
* Rejestruje zmiany trybu graficznego
* Powiela sun.awt.DisplayChangedListener dla niewrażliwości
* od wpływu zmian przyszłych implementacji (sun.awt.*)
*
* Wersje od 1.1 używają refleksji.
* @author Olamagato
* @version 1.1
* @since 2006
*/
public interface DisplayListener
{
/**
* Informuje o zmianie rozdzielczości ekranu
*/
void displayChanged();
/**
* Informuje o zmianie palety kolorów ekranu
*/
void paletteChanged();
/**
* Intefejs pozwalający zmieniać odbieranie zdarzeń zmian grafiki.
*/
interface Switch
{
/**
* Włącza odbieranie zmian.
* @return true jeżeli udało się włączyć odbieranie zdarzeń
*/
boolean enable();
/**
* Wyłącza odbieranie zmian.
* @return true jeżeli udało się wyłączyć odbieranie zdarzeń
*/
boolean disable();
} //interface Switch
/**
* Pośredniczy w informowaniu o zmianach trybu graficznego z
* GraphicsEnvironment jeżeli jest on typu SunGraphicsEnvironment.
* Włącza i wyłacza informowanie o zmianach.
*/
@SuppressWarnings("UseOfSystemOutOrSystemErr")
class To implements Switch
{
/**
* Tworzy pośrednika do docelowego listenera.
* @param listener klasa implementująca DisplayListener
*/
public To(DisplayListener listener)
{
if(listener == null)
throw new IllegalArgumentException("listener = null");
this.listener = listener;
debugNullCheck("listener", this.listener);
this.ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
debugNullCheck("ge", this.ge);
initReflection();
}
//#ifndef REFLECTION
// /**
// * Rejestruje obserwatora dla zmian trybu graficznego
// * Nadmiarowe rejestracje są ignorowane.
// */
// @Override public synchronized boolean enable()
// {
// if(listener == null) return false;
// if(!registered && ge instanceof SunGraphicsEnvironment)
// {
// final SunGraphicsEnvironment sge = (SunGraphicsEnvironment)ge;
// sge.addDisplayChangedListener(speaker);
// registered = true;
// }
// return registered;
// }
//
// /**
// * Wyrejestrowuje obserwatora zmian trybu graficznego
// * Nadmiarowe wyrejestrowania są ignorowane.
// */
// @Override public synchronized boolean disable()
// {
// if(listener == null) return false;
// if(registered && ge instanceof SunGraphicsEnvironment)
// {
// final SunGraphicsEnvironment sge = (SunGraphicsEnvironment)ge;
// sge.removeDisplayChangedListener(speaker);
// registered = false;
// }
// return registered;
// }
//#elif
/**
* Rejestruje obserwatora dla zmian trybu graficznego
* Nadmiarowe rejestracje są ignorowane.
* @return true jeżeli listener został/jest zarejestrowany
*/
@Override public synchronized boolean enable()
{
System.out.println("Próba włączenia listenera grafiki...");
debugNullCheck("listener", listener);
if(listener == null) return false;
else if(registered)
{
System.out.println("Listener był zarejestrowany.");
return true;
}
else if(hasMethod(ge, addDisplayChangedListener, false))
{
System.out.println("1:Grafika gotowa do rejestracji...");
try { addDisplayChangedListener.invoke(ge, speaker); }
catch(IllegalAccessException | IllegalArgumentException
| InvocationTargetException ex)
{
System.out.println("2:Nie zarejestrowano listenera!\n"
+ ex.toString());
return false;
}
registered = true;
System.out.println("3:Zarejestrowano listenera grafiki.");
return true;
}
else System.out.println("4:Brak metody addDisplayChangedListener!");
return false;
}
/**
* Wyrejestrowuje obserwatora zmian trybu graficznego
* Nadmiarowe wyrejestrowania są ignorowane.
* @return true jeżeli listener jest niezarejestrowany
*/
@Override public synchronized boolean disable()
{
System.out.println("Próba wyłączenia listenera grafiki...");
debugNullCheck("listener", listener);
if(listener == null) return false;
else if(!registered)
{
System.out.println("Listener nie był zarejestrowany.");
return true;
}
else if(hasMethod(ge, removeDisplayChangedListener, false))
{
System.out.println("5:Grafika gotowa do wyrejestrowania...");
try { removeDisplayChangedListener.invoke(ge, speaker); }
catch(IllegalAccessException | IllegalArgumentException
| InvocationTargetException ex)
{
System.out.println("6:Nie wyrejestrowano listenera!");
return false;
}
registered = false;
System.out.println("7:Wyrejestrowano listenera grafiki.");
return true;
}
else System.out.println("8:Brak metody"
+ " removeDisplayChangedListener!");
return false;
}
//#endif
//pozwala na wielokrotne rejestracje bez ryzyka wykrzaczenia
private boolean registered;
//odnośnik do klasy powiadamianej o zmianach
private DisplayListener listener;
//Zachowuje stan środowiska gdyby nie był już dostępny
//w czasie wyrejestrowywania
private GraphicsEnvironment ge;
//#ifdef REFLECTION
////// Reflection //////
private static boolean debugNullCheck(String name, Object object)
{
final boolean notNull = object != null;
System.out.println(name + (notNull ? " != " : " == ") + "NULL");
return notNull;
}
/**
* Sprawdza czy obiekt zawiera metodę find przez uzyskanie dostępu
* do nazw wszystkich metod i porównanie par występujących argumentów.
* @param checked sprawdzany obiekt
* @param find metoda do wyszukania
* @param declared true jeżeli wyszukane mają być wszystkie metody
* @return true jeżeli obiekt implementuje szukaną metodę
*/
private static boolean hasMethod(Object checked, Method find,
boolean declared)
{
Class<?> c = checked.getClass();
String findName = find.getName();
Class<?>[] findParams = find.getParameterTypes();
for(Method next: declared ? c.getDeclaredMethods() : c.getMethods())
if(next.getName().equals(findName)
&& Arrays.equals(next.getParameterTypes(), findParams))
return true;
return false;
}
private void initReflection()
{
try
{
displayChangedListenerClass =
Class.forName("sun.awt.DisplayChangedListener");
debugNullCheck("displayChangedListenerClass",
displayChangedListenerClass);
ClassLoader listenerClassLoader = //NULL?
displayChangedListenerClass.getClassLoader();
debugNullCheck("listenerClassLoader", listenerClassLoader);
speaker = Proxy.newProxyInstance(listenerClassLoader,
new Class<?>[] { displayChangedListenerClass },
new InvocationHandler()
{
@Override public Object invoke(Object proxy,
Method method, Object[] args) throws Throwable
{
final String methodName = method.getName();
if(method.getParameterTypes().length == 0)
switch(methodName)
{
case "displayChanged":
listener.displayChanged(); break;
case "paletteChanged":
listener.paletteChanged(); break;
}
return null; //void
}
}); ///*DisplayChangedListener*/ Object speaker
debugNullCheck("speaker", speaker);
addDisplayChangedListener = ge.getClass()
.getMethod("addDisplayChangedListener",
displayChangedListenerClass);
// addDisplayChangedListener.setAccessible(true);
debugNullCheck("addDisplayChangedListener",
addDisplayChangedListener);
removeDisplayChangedListener = ge.getClass()
.getMethod("removeDisplayChangedListener",
displayChangedListenerClass);
// removeDisplayChangedListener.setAccessible(true);
debugNullCheck("removeDisplayChangedListener",
removeDisplayChangedListener);
} //try
catch(ClassNotFoundException | NoSuchMethodException
| SecurityException ex)
{
System.out.println("9:Inicjacja refleksji nieudana.");
Logger.getLogger(DisplayListener.class.getName()).
log(Level.SEVERE, null, ex);
throw new RuntimeException(ex);
}
} //init
private Class<?> displayChangedListenerClass;
private Method addDisplayChangedListener;
private Method removeDisplayChangedListener;
private /*DisplayChangedListener*/ Object speaker;
//#elif
// private DisplayChangedListener speaker =
// new DisplayChangedListener()
// {
// /**
// * Wywoływana tylko przez sun.awt.DisplayChangedListener
// */
// @Override public void displayChanged()
// { listener.displayChanged(); }
// /**
// * Wywoływana tylko przez sun.awt.DisplayChangedListener
// */
// @Override public void paletteChanged()
// { listener.paletteChanged(); }
// };
//#endif
public static void main(String[] args)
{ SwingUtilities.invokeLater(new TestZmianyGrafiki()); }
} //class DisplayListener.To implements Switch
} //public interface DisplayListener
@SuppressWarnings(
{"UseOfSystemOutOrSystemErr", "MultipleTopLevelClassesInFile"} )
class TestZmianyGrafiki
extends WindowAdapter implements DisplayListener, Runnable
{
public TestZmianyGrafiki()
{
frame = new JFrame();
text = new JTextArea();
listening = new DisplayListener.To(this);
}
@Override public void run()
{
try { UIManager.setLookAndFeel(
UIManager.getSystemLookAndFeelClassName()); }
catch(UnsupportedLookAndFeelException | ClassNotFoundException |
InstantiationException | IllegalAccessException e) {}
final Font f = text.getFont();
text.setFont(f.deriveFont(f.getSize2D() * 2));
text.setBackground(SystemColor.desktop);
text.setForeground(SystemColor.windowText);
text.setText(changed("Stan początkowy"));
text.setEditable(false);
frame.setTitle("Rejestrator zmiany grafiki");
frame.setPreferredSize(new Dimension(712, 400));
frame.getContentPane().add(new JScrollPane(text));
frame.pack();
frame.setLocationRelativeTo(null);
// frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
//bez automatycznego windowClosed|JVM exit
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.addWindowListener(this);
frame.setVisible(true);
listening.enable();
System.out.println("Oczekiwanie na zmiany rozdzielczości/palety...\n");
}
@Override public void windowClosing(WindowEvent e)
{
System.out.println("Zamykanie okna...");
if(listening.disable())
{
frame.setVisible(false); //nadmiarowe dla dispose()
frame.dispose(); //odpalenie windowClosed via GUI
}
}
@Override public void windowClosed(WindowEvent e)
{
System.out.println("Zamknięto okno...");
frame.removeWindowListener(this);
}
@Override public void displayChanged() { show(HEAD); }
@Override public void paletteChanged() { show(HEAD); }
private void show(final String head) { text.append(changed(head)); }
private static String changed(final String text)
{
final Toolkit t = Toolkit.getDefaultToolkit();
final Dimension scr = t.getScreenSize();
final int bits = t.getColorModel().getPixelSize();
return String.format("%s: %s%nWymiary: %d x %d, "
+ "rozmiar piksela (wg JVM): %d-bit%n", toStr(new Date()),
text, scr.width, scr.height, bits);
}
private static String toStr(Date time)
{
return DateFormat.getTimeInstance(DateFormat.MEDIUM)
.format(time);
}
private static final String HEAD = "Zmieniono tryb graficzny";
private JFrame frame;
private JTextArea text;
private To listening;
} //class TestZmianyGrafiki implements DisplayListener
Logi do System.out dają coś takiego (debug podobnie):
listener != NULL
ge != NULL
displayChangedListenerClass != NULL
listenerClassLoader == NULL
speaker != NULL
addDisplayChangedListener != NULL
removeDisplayChangedListener != NULL
Próba włączenia listenera grafiki...
listener != NULL
1:Grafika gotowa do rejestracji...
2:Nie zarejestrowano listenera!
java.lang.reflect.InvocationTargetException
Oczekiwanie na zmiany rozdzielczości/palety...Zamykanie okna...
Próba wyłączenia listenera grafiki...
listener != NULL
Listener nie był zarejestrowany.
Zamknięto okno...
Jak widać displayChangedListenerClass wydaje się ok, ale problem robi listenerClassLoader, który jest nullem dzięki czemu tworzony na jego podstawie obiekt
speaker = Proxy.newProxyInstance(...), też właściwie dostaje null (czego nie bardzo widać).
Cała reszta operacji nie może być udana nie mogąc zarejestrować oryginalnego listenera.
Ma ktoś z was jakiś pomysł jak się dorwać do tego poprawnego classloadera? Ewentualnie gdzie jeszcze coś skopałem.