Windows: ¿cómo obtener una lista de todas las ventanas visibles?

(por todos los medios, vuelva a etiquetar con la tecnología relevante: no sé cuáles son 🙂

Probablemente venga más tarde con preguntas más detalladas, sobre detalles específicos, pero por el momento estoy tratando de captar el “outlook general”: estoy buscando una manera de enumerar “ventanas visibles reales” en Windows. Por “ventana visible real” quiero decir exactamente eso: lo que un usuario llamaría una “ventana”. Necesito una forma de obtener una lista de todas estas ventanas visibles, en orden Z.

Tenga en cuenta que realmente necesito hacer eso. Ya lo hice en OS X (donde es un verdadero dolor de cabeza, especialmente si desea soportar OS X 10.4, porque OS X no tiene la conveniente API de Windows) y ahora tengo que hacerlo en Windows.

Aquí hay un ejemplo, supongamos que hay tres ventanas visibles en la pantalla, como esta:

+------------------------------------------+ | | | +=============+ | | | | | | | A +--------------------------+ | | | | | C | | B | | | +--------------------------+ | | | | +-----------| |----------------+ | | +-------------+ 

Entonces necesito recuperar una lista como esta:

  windows B is at (210,40) windows A is at (120,20) windows C is at (0,0) 

Luego, si el usuario (o el SO) lleva la ventana A al frente, se convierte en:

  +------------------------------------------+ | | | +=============+ | | | | | | | A |---------------------+ | | | | | C | | B | | | |---------------------+ | | | | +-----------| |----------------+ | | +-------------+ 

Y obtengo (idealmente) una callback dándome esto:

 windows A is at (120,20) windows B is at (210,40) windows C is at (0,0) 

Hacer esto bajo OS X requiere el uso de hacks increíblemente raros (como ordenar al usuario que active “Habilitar acceso para dispositivo de asistencia” ) pero lo hice bajo OS X y funciona (bajo OS X, no lo hice). Administrar para obtener una callback cada vez que se producen algunos cambios de ventana, por lo que estoy sondeando, pero lo hice funcionar).

Ahora quiero hacer esto en Windows (de verdad, no tengo dudas al respecto) y tengo algunas preguntas:

  • ¿Se puede hacer esto?

  • ¿hay API de Windows bien documentadas (y funcionando según sus especificaciones) que permitan hacer eso?

  • ¿Es fácil registrar una callback cada vez que cambia una ventana? (si se cambia de tamaño, se mueve, se lleva hacia atrás / frente o si aparece una nueva ventana emergente, etc.)

  • ¿qué serían las trampas?

Sé que esta pregunta no es específica, y es por eso que traté de describir mi problema lo más claramente posible (incluido un buen arte ASCII por el cual puedes votar esto): por ahora estoy viendo el “outlook general”. Quiero saber qué hace eso bajo Windows.

Pregunta adicional: imagina que necesitarías escribir un pequeño .exe escribiendo los nombres / posición / tamaño de las ventanas en un archivo temporal cada vez que haya un cambio de ventana en la pantalla, cuánto tiempo duraría ese progtwig aproximadamente en tu idioma de elección y por cuánto tiempo necesitarías escribirlo?

(una vez más, estoy tratando de obtener la “visión general” para entender qué está funcionando aquí)

Para enumerar las ventanas de nivel superior, debe usar EnumWindows en lugar de GetTopWindow / GetNextWindow, ya que EnumWindows devuelve una vista consistente del estado de la ventana. Corre el riesgo de obtener información inconsistente (como informar sobre ventanas eliminadas) o bucles infinitos usando GetTopWindow / GetNextWindow, cuando las ventanas cambian de orden z durante la iteración.

EnumWindows usa una callback. En cada llamada de la callback se obtiene un identificador de ventana. Las coordenadas de pantalla de la ventana se pueden recuperar pasando ese identificador a GetWindowRect . Su callback crea una lista de las posiciones de las ventanas en orden z.

Puede usar sondeo y comstackr la lista de ventanas repetidamente. O bien, configura un CBTHook para recibir notificaciones de cambios de ventana. No todas las notificaciones CBT darán lugar a cambios en el orden, posición o visibilidad de las ventanas de nivel superior, por lo que es aconsejable volver a EnmWindows para comstackr una nueva lista de posiciones de ventana en orden z y comparar esto con la lista anterior antes de continuar con la lista, de modo que un procesamiento posterior solo se realiza cuando se produce un cambio real.

Tenga en cuenta que con el enganche, no puede mezclar 32 y 64 bits. Si está ejecutando una aplicación de 32 bits, recibirá notificaciones de procesos de 32 bits. Del mismo modo para 64 bits. Por lo tanto, si desea monitorear todo el sistema en una máquina de 64 bits, parecería que es necesario ejecutar dos aplicaciones. Mi razonamiento proviene de leer esto:

SetWindowsHookEx se puede utilizar para inyectar una DLL en otro proceso. No se puede inyectar una DLL de 32 bits en un proceso de 64 bits, y una DLL de 64 bits no se puede inyectar en un proceso de 32 bits. Si una aplicación requiere el uso de enganches en otros procesos, se requiere que una aplicación de 32 bits llame a SetWindowsHookEx para inyectar una DLL de 32 bits en procesos de 32 bits, y una aplicación de 64 bits llame a SetWindowsHookEx para inyectar un 64-bit DLL en procesos de 64 bits. Los archivos DLL de 32 bits y 64 bits deben tener diferentes nombres. (Desde la página de la API de SetWindowsHookEx).

Como está implementando esto en Java, es posible que desee ver JNA : hace que el acceso de escritura a las bibliotecas nativas sea mucho más simple (llamar al código en java) y elimina la necesidad de su propia DLL JNI nativa.

EDITAR: Usted preguntó cuánto código es y cuánto tiempo escribir. Aquí está el código en java

 import com.sun.jna.Native; import com.sun.jna.Structure; import com.sun.jna.win32.StdCallLibrary; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; public class Main { public static void main(String[] args) { Main m = new Main(); final List inflList = new ArrayList(); final List order = new ArrayList(); int top = User32.instance.GetTopWindow(0); while (top != 0) { order.add(top); top = User32.instance.GetWindow(top, User32.GW_HWNDNEXT); } User32.instance.EnumWindows(new WndEnumProc() { public boolean callback(int hWnd, int lParam) { if (User32.instance.IsWindowVisible(hWnd)) { RECT r = new RECT(); User32.instance.GetWindowRect(hWnd, r); if (r.left > -32000) { // If it's not minimized byte[] buffer = new byte[1024]; User32.instance.GetWindowTextA(hWnd, buffer, buffer.length); String title = Native.toString(buffer); inflList.add(new WindowInfo(hWnd, r, title)); } } return true; } }, 0); Collections.sort(inflList, new Comparator() { public int compare(WindowInfo o1, WindowInfo o2) { return order.indexOf(o1.hwnd)-order.indexOf(o2.hwnd); } }); for (WindowInfo w : inflList) { System.out.println(w); } } public static interface WndEnumProc extends StdCallLibrary.StdCallCallback { boolean callback(int hWnd, int lParam); } public static interface User32 extends StdCallLibrary { final User32 instance = (User32) Native.loadLibrary ("user32", User32.class); final int GW_HWNDNEXT = 2; boolean EnumWindows(WndEnumProc wndenumproc, int lParam); boolean IsWindowVisible(int hWnd); int GetWindowRect(int hWnd, RECT r); void GetWindowTextA(int hWnd, byte[] buffer, int buflen); int GetTopWindow(int hWnd); int GetWindow(int hWnd, int flag); } public static class RECT extends Structure { public int left, top, right, bottom; } public static class WindowInfo { public final int hwnd; public final RECT rect; public final String title; public WindowInfo(int hwnd, RECT rect, String title) { this.hwnd = hwnd; this.rect = rect; this.title = title; } public String toString() { return String.format("(%d,%d)-(%d,%d) : \"%s\"", rect.left, rect.top, rect.right, rect.bottom, title); } } } 

Realicé la mayoría de las clases internas de clases e interfaces relacionadas para mantener el ejemplo compacto y en pasta para la comstackción inmediata. En una implementación real, serían clases regulares de alto nivel. La aplicación de línea de comandos imprime las ventanas visibles y su posición. Lo ejecuté tanto en jvm de 32 bits como en 64 bits, y obtuve los mismos resultados para cada uno.

EDIT2: Código actualizado para incluir orden z. Utiliza GetNextWindow. En una aplicación de producción, probablemente debería llamar a GetNextWindow dos veces para los valores siguientes y anteriores y verificar que sean coherentes y que sean identificadores de ventanas válidos.

¿Se puede hacer esto?

Sí, aunque tendrías que registrar un enlace para obtener lo que deseas con respecto a una callback. Probablemente necesites usar un Callback Hook CBTProc , que se llama siempre que:

activar, crear, destruir, minimizar, maximizar, mover o dimensionar una ventana; antes de completar un comando del sistema; antes de eliminar un evento de mouse o teclado de la cola de mensajes del sistema; antes de configurar el foco del teclado; o antes de sincronizar con la cola de mensajes del sistema

Sin embargo, tenga en cuenta que no creo que dichos enganches funcionen en las ventanas de la Consola porque son el dominio del núcleo, no de Win32.

¿hay API de Windows bien documentadas (y funcionando según sus especificaciones) que permitan hacer eso?

Sí. Puede usar las funciones GetTopWindow y GetNextWindow para obtener todos los identificadores de ventana en el escritorio en el orden Z correcto.

¿Es fácil registrar una callback cada vez que cambia una ventana? (si se cambia de tamaño, se mueve, se lleva hacia atrás / frente o si aparece una nueva ventana emergente, etc.)

Ver la primera respuesta 🙂

¿qué serían las trampas?

Ver la primera respuesta 🙂

Pregunta adicional: imagina que necesitarías escribir un pequeño .exe escribiendo los nombres / posición / tamaño de las ventanas en un archivo temporal cada vez que haya un cambio de ventana en la pantalla, cuánto tiempo duraría ese progtwig aproximadamente en tu idioma de elección y por cuánto tiempo necesitarías escribirlo?

Unos cientos de líneas de C, y un par de horas. Aunque tendré que usar alguna forma de sondeo, nunca antes he hecho anzuelos. Si necesito los ganchos me tomaría algo más de tiempo.

Recuerdo que en 2006 había una utilidad WinObj como parte de sysinternals que posiblemente hizo lo que quería. Parte de estas utilidades fueron provistas con el código fuente por autor (Mark Russinovich).

Desde ese momento, su compañía fue comprada por Microsoft, así que no sé si la fuente estará disponible.

También vale la pena consultar lo siguiente:

http://msdn.microsoft.com/en-us/library/aa264396(VS.60).aspx

http://www.codeproject.com/KB/dialog/windowfinder.aspx