/* * Copyright 2000-2016 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.openapi.wm.impl; import com.intellij.execution.configurations.GeneralCommandLine; import com.intellij.execution.util.ExecUtil; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.CharsetToolkit; import com.intellij.openapi.wm.IdeFrame; import com.intellij.openapi.wm.WindowManager; import org.jetbrains.annotations.Nullable; import sun.misc.Unsafe; import javax.swing.*; import java.awt.*; import java.awt.peer.ComponentPeer; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.regex.Matcher; import java.util.regex.Pattern; import static com.intellij.util.ArrayUtil.newLongArray; public class X11UiUtil { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.wm.impl.X11UiUtil"); private static final int True = 1; private static final int False = 0; private static final long None = 0; private static final long XA_ATOM = 4; private static final long XA_WINDOW = 33; private static final int CLIENT_MESSAGE = 33; private static final int FORMAT_BYTE = 8; private static final int FORMAT_LONG = 32; private static final long EVENT_MASK = (3L << 19); private static final long NET_WM_STATE_TOGGLE = 2; @SuppressWarnings("SpellCheckingInspection") private static class Xlib { private Unsafe unsafe; private Method XGetWindowProperty; private Method XFree; private Method RootWindow; private Method XSendEvent; private Method getWindow; private Method getScreenNumber; private Method awtLock; private Method awtUnlock; private long display; private long UTF8_STRING; private long NET_SUPPORTING_WM_CHECK; private long NET_WM_NAME; private long NET_WM_ALLOWED_ACTIONS; private long NET_WM_STATE; private long NET_WM_ACTION_FULLSCREEN; private long NET_WM_STATE_FULLSCREEN; @Nullable private static Xlib getInstance() { Class<? extends Toolkit> toolkitClass = Toolkit.getDefaultToolkit().getClass(); if (!SystemInfo.isXWindow || !"sun.awt.X11.XToolkit".equals(toolkitClass.getName())) { return null; } try { Xlib x11 = new Xlib(); // reflect on Xlib method wrappers and important structures Class<?> XlibWrapper = Class.forName("sun.awt.X11.XlibWrapper"); x11.unsafe = (Unsafe)field(XlibWrapper, "unsafe").get(null); x11.XGetWindowProperty = method(XlibWrapper, "XGetWindowProperty", 12); x11.XFree = method(XlibWrapper, "XFree", 1); x11.RootWindow = method(XlibWrapper, "RootWindow", 2); x11.XSendEvent = method(XlibWrapper, "XSendEvent", 5); Class<?> XBaseWindow = Class.forName("sun.awt.X11.XBaseWindow"); x11.getWindow = method(XBaseWindow, "getWindow"); x11.getScreenNumber = method(XBaseWindow, "getScreenNumber"); x11.display = (Long)method(toolkitClass, "getDisplay").invoke(null); x11.awtLock = method(toolkitClass, "awtLock"); x11.awtUnlock = method(toolkitClass, "awtUnlock"); // intern atoms Class<?> XAtom = Class.forName("sun.awt.X11.XAtom"); Method get = method(XAtom, "get", String.class); Field atom = field(XAtom, "atom"); x11.UTF8_STRING = (Long)atom.get(get.invoke(null, "UTF8_STRING")); x11.NET_SUPPORTING_WM_CHECK = (Long)atom.get(get.invoke(null, "_NET_SUPPORTING_WM_CHECK")); x11.NET_WM_NAME = (Long)atom.get(get.invoke(null, "_NET_WM_NAME")); x11.NET_WM_ALLOWED_ACTIONS = (Long)atom.get(get.invoke(null, "_NET_WM_ALLOWED_ACTIONS")); x11.NET_WM_STATE = (Long)atom.get(get.invoke(null, "_NET_WM_STATE")); x11.NET_WM_ACTION_FULLSCREEN = (Long)atom.get(get.invoke(null, "_NET_WM_ACTION_FULLSCREEN")); x11.NET_WM_STATE_FULLSCREEN = (Long)atom.get(get.invoke(null, "_NET_WM_STATE_FULLSCREEN")); // check for _NET protocol support Long netWmWindow = x11.getNetWmWindow(); if (netWmWindow == null) { LOG.info("_NET protocol is not supported"); return null; } return x11; } catch (Throwable t) { LOG.info("cannot initialize", t); } return null; } private long getRootWindow(long screen) throws Exception { awtLock.invoke(null); try { return (Long)RootWindow.invoke(null, display, screen); } finally { awtUnlock.invoke(null); } } @Nullable private Long getNetWmWindow() throws Exception { long rootWindow = getRootWindow(0); long[] values = getLongArrayProperty(rootWindow, NET_SUPPORTING_WM_CHECK, XA_WINDOW); return values != null && values.length > 0 ? values[0] : null; } @Nullable private long[] getLongArrayProperty(long window, long name, long type) throws Exception { return getWindowProperty(window, name, type, FORMAT_LONG); } @Nullable private String getUtfStringProperty(long window, long name) throws Exception { byte[] bytes = getWindowProperty(window, name, UTF8_STRING, FORMAT_BYTE); return bytes != null ? new String(bytes, CharsetToolkit.UTF8_CHARSET) : null; } @Nullable private <T> T getWindowProperty(long window, long name, long type, long expectedFormat) throws Exception { T property = null; long data = unsafe.allocateMemory(64); awtLock.invoke(null); try { unsafe.setMemory(data, 64, (byte)0); int result = (Integer)XGetWindowProperty.invoke( null, display, window, name, 0L, 65535L, (long)False, type, data, data + 8, data + 16, data + 24, data + 32); if (result == 0) { int format = unsafe.getInt(data + 8); long pointer = SystemInfo.is64Bit ? unsafe.getLong(data + 32) : unsafe.getInt(data + 32); if (pointer != None && format == expectedFormat) { int length = SystemInfo.is64Bit ? (int)unsafe.getLong(data + 16) : unsafe.getInt(data + 16); if (format == FORMAT_BYTE) { byte[] bytes = new byte[length]; for (int i = 0; i < length; i++) bytes[i] = unsafe.getByte(pointer + i); property = (T)bytes; } else if (format == FORMAT_LONG) { long[] values = newLongArray(length); for (int i = 0; i < length; i++) { values[i] = SystemInfo.is64Bit ? unsafe.getLong(pointer + 8 * i) : unsafe.getInt(pointer + 4 * i); } property = (T)values; } else if (format != None) { LOG.info("unexpected format: " + format); } } if (pointer != None) XFree.invoke(null, pointer); } } finally { awtUnlock.invoke(null); unsafe.freeMemory(data); } return property; } private void sendClientMessage(long target, long window, long type, long... data) throws Exception { assert data.length <= 5; long event = unsafe.allocateMemory(128); awtLock.invoke(null); try { unsafe.setMemory(event, 128, (byte)0); unsafe.putInt(event, CLIENT_MESSAGE); if (!SystemInfo.is64Bit) { unsafe.putInt(event + 8, True); unsafe.putInt(event + 16, (int)window); unsafe.putInt(event + 20, (int)type); unsafe.putInt(event + 24, FORMAT_LONG); for (int i = 0; i < data.length; i++) { unsafe.putInt(event + 28 + 4 * i, (int)data[i]); } } else { unsafe.putInt(event + 16, True); unsafe.putLong(event + 32, window); unsafe.putLong(event + 40, NET_WM_STATE); unsafe.putInt(event + 48, FORMAT_LONG); for (int i = 0; i < data.length; i++) { unsafe.putLong(event + 56 + 8 * i, data[i]); } } XSendEvent.invoke(null, display, target, false, EVENT_MASK, event); } finally { awtUnlock.invoke(null); unsafe.freeMemory(event); } } } @Nullable private static final Xlib X11 = Xlib.getInstance(); // WM detection and patching @Nullable public static String getWmName() { if (X11 == null) return null; try { Long netWmWindow = X11.getNetWmWindow(); if (netWmWindow != null) { return X11.getUtfStringProperty(netWmWindow, X11.NET_WM_NAME); } } catch (Throwable t) { LOG.info("cannot get WM name", t); } return null; } @SuppressWarnings("SpellCheckingInspection") public static void patchDetectedWm(String wmName) { if (X11 == null || !Registry.is("ide.x11.override.wm")) return; try { if (wmName.startsWith("Mutter") || "Muffin".equals(wmName) || "GNOME Shell".equals(wmName)) { setWM("MUTTER_WM", "METACITY_WM"); } else if ("Marco".equals(wmName)) { setWM("MARCO_WM", "METACITY_WM"); } else if ("awesome".equals(wmName)) { String version = getAwesomeWMVersion(); if (StringUtil.compareVersionNumbers(version, "3.5") >= 0) { setWM("SAWFISH_WM"); } else if (version != null) { setWM("OTHER_NONREPARENTING_WM", "LG3D_WM"); } } } catch (Throwable t) { LOG.warn(t); } } private static void setWM(String... wmConstants) throws Exception { Class<?> xwmClass = Class.forName("sun.awt.X11.XWM"); Object xwm = method(xwmClass, "getWM").invoke(null); if (xwm != null) { for (String wmConstant : wmConstants) { try { Field wm = field(xwmClass, wmConstant); Object id = wm.get(null); if (id != null) { field(xwmClass, "awt_wmgr").set(null, id); field(xwmClass, "WMID").set(xwm, id); LOG.info("impersonated WM: " + wmConstant); break; } } catch (NoSuchFieldException ignore) { } } } } @Nullable private static String getAwesomeWMVersion() { try { String version = ExecUtil.execAndReadLine(new GeneralCommandLine("awesome", "--version")); if (version != null) { Matcher m = Pattern.compile("awesome v([0-9.]+)").matcher(version); if (m.find()) { return m.group(1); } } } catch (Throwable t) { LOG.warn(t); } return null; } // full-screen support public static boolean isFullScreenSupported() { if (X11 == null) return false; IdeFrame[] frames = WindowManager.getInstance().getAllProjectFrames(); if (frames.length == 0) return true; // no frame to check the property so be optimistic here return frames[0] instanceof JFrame && hasWindowProperty((JFrame)frames[0], X11.NET_WM_ALLOWED_ACTIONS, X11.NET_WM_ACTION_FULLSCREEN); } public static boolean isInFullScreenMode(JFrame frame) { return X11 != null && hasWindowProperty(frame, X11.NET_WM_STATE, X11.NET_WM_STATE_FULLSCREEN); } private static boolean hasWindowProperty(JFrame frame, long name, long expected) { if (X11 == null) return false; try { ComponentPeer peer = frame.getPeer(); long window = (Long)X11.getWindow.invoke(peer); long[] values = X11.getLongArrayProperty(window, name, XA_ATOM); if (values != null) { for (long value : values) { if (value == expected) return true; } } return false; } catch (Throwable t) { LOG.info("cannot check window property", t); return false; } } public static void toggleFullScreenMode(JFrame frame) { if (X11 == null) return; try { ComponentPeer peer = frame.getPeer(); long window = (Long)X11.getWindow.invoke(peer); long screen = (Long)X11.getScreenNumber.invoke(peer); long rootWindow = X11.getRootWindow(screen); X11.sendClientMessage(rootWindow, window, X11.NET_WM_STATE, NET_WM_STATE_TOGGLE, X11.NET_WM_STATE_FULLSCREEN); } catch (Throwable t) { LOG.info("cannot toggle mode", t); } } // reflection utilities private static Method method(Class<?> aClass, String name, Class<?>... parameterTypes) throws Exception { while (aClass != null) { try { Method method = aClass.getDeclaredMethod(name, parameterTypes); method.setAccessible(true); return method; } catch (NoSuchMethodException e) { aClass = aClass.getSuperclass(); } } throw new NoSuchMethodException(name); } private static Method method(Class<?> aClass, String name, int parameters) throws Exception { for (Method method : aClass.getDeclaredMethods()) { if (name.equals(method.getName()) && method.getParameterTypes().length == parameters) { method.setAccessible(true); return method; } } throw new NoSuchMethodException(name); } private static Field field(Class<?> aClass, String name) throws Exception { Field field = aClass.getDeclaredField(name); field.setAccessible(true); return field; } }