| // Copyright 2012 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // This file defines utility functions for X11 (Linux only). This code has been |
| // ported from XCB since we can't use XCB on Ubuntu while its 32-bit support |
| // remains woefully incomplete. |
| |
| #include "ui/base/x/x11_util.h" |
| |
| #include <sys/ipc.h> |
| #include <sys/shm.h> |
| |
| #include <bitset> |
| #include <limits> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/command_line.h" |
| #include "base/compiler_specific.h" |
| #include "base/containers/contains.h" |
| #include "base/containers/span.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/memory/ref_counted_memory.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/notreached.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/trace_event/trace_event.h" |
| #include "build/build_config.h" |
| #include "third_party/skia/include/core/SkBitmap.h" |
| #include "third_party/skia/include/core/SkImageInfo.h" |
| #include "ui/base/cursor/mojom/cursor_type.mojom-shared.h" |
| #include "ui/display/util/gpu_info_util.h" |
| #include "ui/events/devices/x11/device_data_manager_x11.h" |
| #include "ui/events/devices/x11/touch_factory_x11.h" |
| #include "ui/gfx/geometry/insets.h" |
| #include "ui/gfx/geometry/point.h" |
| #include "ui/gfx/geometry/rect.h" |
| #include "ui/gfx/geometry/size.h" |
| #include "ui/gfx/image/image_skia.h" |
| #include "ui/gfx/switches.h" |
| #include "ui/gfx/x/atom_cache.h" |
| #include "ui/gfx/x/connection.h" |
| #include "ui/gfx/x/screensaver.h" |
| #include "ui/gfx/x/shm.h" |
| #include "ui/gfx/x/xproto.h" |
| |
| #if BUILDFLAG(IS_FREEBSD) |
| #include <sys/sysctl.h> |
| #include <sys/types.h> |
| #endif |
| |
| namespace ui { |
| namespace { |
| |
| // Constants that are part of EWMH. |
| constexpr int kNetWMStateAdd = 1; |
| constexpr int kNetWMStateRemove = 0; |
| |
| // Returns whether the X11 Screen Saver Extension can be used to disable the |
| // screen saver. |
| bool IsX11ScreenSaverAvailable() { |
| // X Screen Saver isn't accessible in headless mode. |
| return !base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kHeadless) && |
| x11::Connection::Get()->screensaver_version() >= |
| std::pair<uint32_t, uint32_t>{1, 1}; |
| } |
| |
| // Returns true if the event has event_x and event_y fields. |
| bool EventHasCoordinates(const x11::Event& event) { |
| return event.As<x11::KeyEvent>() || event.As<x11::ButtonEvent>() || |
| event.As<x11::MotionNotifyEvent>() || event.As<x11::CrossingEvent>() || |
| event.As<x11::Input::LegacyDeviceEvent>() || |
| event.As<x11::Input::DeviceEvent>() || |
| event.As<x11::Input::CrossingEvent>(); |
| } |
| |
| } // namespace |
| |
| size_t RowBytesForVisualWidth(const x11::Connection::VisualInfo& visual_info, |
| int width) { |
| auto bpp = visual_info.format->bits_per_pixel; |
| auto align = visual_info.format->scanline_pad; |
| size_t row_bits = bpp * width; |
| row_bits += (align - (row_bits % align)) % align; |
| return (row_bits + 7) / 8; |
| } |
| |
| void DrawPixmap(x11::Connection* connection, |
| x11::VisualId visual, |
| x11::Drawable drawable, |
| x11::GraphicsContext gc, |
| const SkPixmap& skia_pixmap, |
| int src_x, |
| int src_y, |
| int dst_x, |
| int dst_y, |
| int width, |
| int height) { |
| // 24 bytes for the PutImage header, an additional 4 bytes in case this is an |
| // extended size request, and an additional 4 bytes in case padding is needed. |
| constexpr size_t kPutImageExtraSize = 32; |
| |
| const auto* visual_info = connection->GetVisualInfoFromId(visual); |
| if (!visual_info) { |
| return; |
| } |
| |
| size_t row_bytes = RowBytesForVisualWidth(*visual_info, width); |
| |
| auto color_type = ColorTypeForVisual(visual); |
| if (color_type == kUnknown_SkColorType) { |
| // TODO(crbug.com/40124639): Add a fallback path in case any users |
| // are running a server that uses visual types for which Skia doesn't have |
| // a corresponding color format. |
| return; |
| } |
| SkImageInfo image_info = |
| SkImageInfo::Make(width, height, color_type, kPremul_SkAlphaType); |
| |
| std::vector<uint8_t> vec(row_bytes * height); |
| SkPixmap pixmap(image_info, vec.data(), row_bytes); |
| skia_pixmap.readPixels(pixmap, src_x, src_y); |
| |
| DCHECK_GT(connection->MaxRequestSizeInBytes(), kPutImageExtraSize); |
| int rows_per_request = |
| (connection->MaxRequestSizeInBytes() - kPutImageExtraSize) / row_bytes; |
| DCHECK_GT(rows_per_request, 1); |
| for (int row = 0; row < height; row += rows_per_request) { |
| size_t n_rows = std::min<size_t>(rows_per_request, height - row); |
| auto data = base::MakeRefCounted<base::RefCountedStaticMemory>( |
| base::span(vec).subspan(row * row_bytes, n_rows * row_bytes)); |
| connection->PutImage({ |
| .format = x11::ImageFormat::ZPixmap, |
| .drawable = drawable, |
| .gc = gc, |
| .width = static_cast<uint16_t>(width), |
| .height = static_cast<uint16_t>(n_rows), |
| .dst_x = static_cast<int16_t>(dst_x), |
| .dst_y = static_cast<int16_t>(dst_y + row), |
| .left_pad = 0, |
| .depth = visual_info->format->depth, |
| .data = data, |
| }); |
| } |
| // Flush since the PutImage requests depend on |vec| being alive. |
| connection->Flush(); |
| } |
| |
| bool IsXInput2Available() { |
| return DeviceDataManagerX11::GetInstance()->IsXInput2Available(); |
| } |
| |
| bool QueryShmSupport() { |
| return x11::Connection::Get()->shm_version() > |
| std::pair<uint32_t, uint32_t>{0, 0}; |
| } |
| |
| int CoalescePendingMotionEvents(const x11::Event& x11_event, |
| x11::Event* last_event) { |
| auto* conn = x11::Connection::Get(); |
| auto* ddmx11 = ui::DeviceDataManagerX11::GetInstance(); |
| int num_coalesced = 0; |
| |
| const auto* motion = x11_event.As<x11::MotionNotifyEvent>(); |
| const auto* device = x11_event.As<x11::Input::DeviceEvent>(); |
| DCHECK(motion || device); |
| DCHECK(!device || device->opcode == x11::Input::DeviceEvent::Motion || |
| device->opcode == x11::Input::DeviceEvent::TouchUpdate); |
| |
| conn->ReadResponses(); |
| for (auto& event : conn->events()) { |
| // There may be non-input events such as ConfigureNotifyEvents and |
| // PropertyNotifyEvents that get interleaved between mouse events, so it is |
| // necessary to skip over those to coalesce as many pending motion events as |
| // possible so mouse dragging is smooth. |
| if (!EventHasCoordinates(event)) { |
| continue; |
| } |
| |
| if (motion) { |
| const auto* next_motion = event.As<x11::MotionNotifyEvent>(); |
| |
| // Discard all but the most recent motion event that targets the same |
| // window with unchanged state. |
| if (next_motion && next_motion->event == motion->event && |
| next_motion->child == motion->child && |
| next_motion->state == motion->state) { |
| *last_event = std::move(event); |
| continue; |
| } |
| } else { |
| auto* next_device = event.As<x11::Input::DeviceEvent>(); |
| if (!next_device) { |
| break; |
| } |
| |
| // If this isn't from a valid device, throw the event away, as |
| // that's what the message pump would do. Device events come in pairs |
| // with one from the master and one from the slave so there will |
| // always be at least one pending. |
| if (!ui::TouchFactory::GetInstance()->ShouldProcessDeviceEvent( |
| *next_device)) { |
| event = x11::Event(); |
| continue; |
| } |
| |
| // Confirm that the motion event is of the same type, is |
| // targeted at the same window, and that no buttons or modifiers |
| // have changed. |
| if (next_device->opcode == device->opcode && |
| !ddmx11->IsCMTGestureEvent(event) && |
| ddmx11->GetScrollClassEventDetail(event) == SCROLL_TYPE_NO_SCROLL && |
| device->event == next_device->event && |
| device->child == next_device->child && |
| device->detail == next_device->detail && |
| device->button_mask == next_device->button_mask && |
| device->mods.base == next_device->mods.base && |
| device->mods.latched == next_device->mods.latched && |
| device->mods.locked == next_device->mods.locked && |
| device->mods.effective == next_device->mods.effective) { |
| *last_event = std::move(event); |
| num_coalesced++; |
| continue; |
| } |
| } |
| break; |
| } |
| |
| return num_coalesced; |
| } |
| |
| void SetUseOSWindowFrame(x11::Window window, bool use_os_window_frame) { |
| // This data structure represents additional hints that we send to the window |
| // manager and has a direct lineage back to Motif, which defined this de facto |
| // standard. We define this struct to match the wire-format (32-bit fields) |
| // rather than the Xlib API (XChangeProperty) format (long fields). |
| typedef struct { |
| uint32_t flags; |
| uint32_t functions; |
| uint32_t decorations; |
| int32_t input_mode; |
| uint32_t status; |
| } MotifWmHints; |
| |
| MotifWmHints motif_hints = {}; |
| // Signals that the reader of the _MOTIF_WM_HINTS property should pay |
| // attention to the value of |decorations|. |
| motif_hints.flags = (1u << 1); |
| motif_hints.decorations = use_os_window_frame ? 1 : 0; |
| |
| std::vector<uint32_t> hints(sizeof(MotifWmHints) / sizeof(uint32_t)); |
| UNSAFE_TODO(memcpy(hints.data(), &motif_hints, sizeof(MotifWmHints))); |
| x11::Atom hint_atom = x11::GetAtom("_MOTIF_WM_HINTS"); |
| x11::Connection::Get()->SetArrayProperty(window, hint_atom, hint_atom, hints); |
| } |
| |
| bool IsShapeExtensionAvailable() { |
| return x11::Connection::Get()->shape().present(); |
| } |
| |
| x11::Window GetX11RootWindow() { |
| return x11::Connection::Get()->default_screen().root; |
| } |
| |
| bool GetCurrentDesktop(int32_t* desktop) { |
| return x11::Connection::Get()->GetPropertyAs( |
| GetX11RootWindow(), x11::GetAtom("_NET_CURRENT_DESKTOP"), desktop); |
| } |
| |
| void SetHideTitlebarWhenMaximizedProperty(x11::Window window, |
| HideTitlebarWhenMaximized property) { |
| x11::Connection::Get()->SetProperty( |
| window, x11::GetAtom("_GTK_HIDE_TITLEBAR_WHEN_MAXIMIZED"), |
| x11::Atom::CARDINAL, static_cast<uint32_t>(property)); |
| } |
| |
| bool GetRawBytesOfProperty(x11::Window window, |
| x11::Atom property, |
| scoped_refptr<base::RefCountedMemory>* out_data, |
| x11::Atom* out_type) { |
| auto future = x11::Connection::Get()->GetProperty({ |
| .window = window, |
| .property = property, |
| // Don't limit the amount of returned data. |
| .long_length = std::numeric_limits<uint32_t>::max(), |
| }); |
| auto response = future.Sync(); |
| if (!response || !response->format) { |
| return false; |
| } |
| // SAFETY: The GetProperty response has a `format` which specified the number |
| // of bits per object in the `value` and `value_len` for the number of |
| // objects, so `value_len * format / 8` gives the number of bytes in `value`. |
| *out_data = UNSAFE_BUFFERS(x11::SizedRefCountedMemory::From( |
| response->value, response->value_len * response->format / 8u)); |
| if (out_type) { |
| *out_type = response->type; |
| } |
| return true; |
| } |
| |
| void SetWindowClassHint(x11::Connection* connection, |
| x11::Window window, |
| const std::string& res_name, |
| const std::string& res_class) { |
| auto str = |
| base::StringPrintf("%s%c%s", res_name.c_str(), '\0', res_class.c_str()); |
| std::vector<char> data(str.data(), UNSAFE_TODO(str.data() + str.size() + 1)); |
| x11::Connection::Get()->SetArrayProperty(window, x11::Atom::WM_CLASS, |
| x11::Atom::STRING, data); |
| } |
| |
| void SetWindowRole(x11::Window window, const std::string& role) { |
| x11::Atom prop = x11::GetAtom("WM_WINDOW_ROLE"); |
| if (role.empty()) { |
| x11::Connection::Get()->DeleteProperty(window, prop); |
| } else { |
| x11::Connection::Get()->SetStringProperty(window, prop, x11::Atom::STRING, |
| role); |
| } |
| } |
| |
| void SetWMSpecState(x11::Window window, |
| bool enabled, |
| x11::Atom state1, |
| x11::Atom state2) { |
| SendClientMessage( |
| window, GetX11RootWindow(), x11::GetAtom("_NET_WM_STATE"), |
| {static_cast<uint32_t>(enabled ? kNetWMStateAdd : kNetWMStateRemove), |
| static_cast<uint32_t>(state1), static_cast<uint32_t>(state2), 1, 0}); |
| } |
| |
| void DoWMMoveResize(x11::Connection* connection, |
| x11::Window root_window, |
| x11::Window window, |
| const gfx::Point& location_px, |
| int direction) { |
| // This handler is usually sent when the window has the implicit grab. We |
| // need to dump it because what we're about to do is tell the window manager |
| // that it's now responsible for moving the window around; it immediately |
| // grabs when it receives the event below. |
| connection->UngrabPointer({x11::Time::CurrentTime}); |
| |
| SendClientMessage(window, root_window, x11::GetAtom("_NET_WM_MOVERESIZE"), |
| {static_cast<uint32_t>(location_px.x()), |
| static_cast<uint32_t>(location_px.y()), |
| static_cast<uint32_t>(direction), 0, 0}); |
| } |
| |
| bool HasWMSpecProperty(const base::flat_set<x11::Atom>& properties, |
| x11::Atom atom) { |
| return properties.find(atom) != properties.end(); |
| } |
| |
| bool GetCustomFramePrefDefault() { |
| // _NET_WM_MOVERESIZE is needed for frame-drag-initiated window movement. |
| if (!x11::Connection::Get()->WmSupportsHint( |
| x11::GetAtom("_NET_WM_MOVERESIZE"))) { |
| return false; |
| } |
| |
| ui::WindowManagerName wm = GuessWindowManager(); |
| // If we don't know which WM is active, conservatively disable custom frames. |
| if (wm == WM_OTHER || wm == WM_UNNAMED) { |
| return false; |
| } |
| |
| // Stacking WMs should use custom frames. |
| return !IsWmTiling(wm); |
| } |
| |
| bool IsWmTiling(WindowManagerName window_manager) { |
| switch (window_manager) { |
| case WM_BLACKBOX: |
| case WM_COMPIZ: |
| case WM_ENLIGHTENMENT: |
| case WM_FLUXBOX: |
| case WM_ICE_WM: |
| case WM_KWIN: |
| case WM_MATCHBOX: |
| case WM_METACITY: |
| case WM_MUFFIN: |
| case WM_MUTTER: |
| case WM_OPENBOX: |
| case WM_XFWM4: |
| // Stacking window managers. |
| return false; |
| |
| case WM_I3: |
| case WM_ION3: |
| case WM_NOTION: |
| case WM_RATPOISON: |
| case WM_STUMPWM: |
| // Tiling window managers. |
| return true; |
| |
| case WM_AWESOME: |
| case WM_QTILE: |
| case WM_XMONAD: |
| case WM_WMII: |
| // Dynamic (tiling and stacking) window managers. Assume tiling. |
| return true; |
| |
| case WM_OTHER: |
| case WM_UNNAMED: |
| // Unknown. Assume stacking. |
| return false; |
| } |
| } |
| |
| bool GetWindowDesktop(x11::Window window, int32_t* desktop) { |
| return x11::Connection::Get()->GetPropertyAs( |
| window, x11::GetAtom("_NET_WM_DESKTOP"), desktop); |
| } |
| |
| WindowManagerName GuessWindowManager() { |
| std::string name = x11::Connection::Get()->GetWmName(); |
| if (name.empty()) { |
| return WM_UNNAMED; |
| } |
| // These names are taken from the WMs' source code. |
| if (name == "awesome") { |
| return WM_AWESOME; |
| } |
| if (name == "Blackbox") { |
| return WM_BLACKBOX; |
| } |
| if (name == "Compiz" || name == "compiz") { |
| return WM_COMPIZ; |
| } |
| if (name == "e16" || name == "Enlightenment") { |
| return WM_ENLIGHTENMENT; |
| } |
| if (name == "Fluxbox") { |
| return WM_FLUXBOX; |
| } |
| if (name == "i3") { |
| return WM_I3; |
| } |
| if (name.starts_with("IceWM")) { |
| return WM_ICE_WM; |
| } |
| if (name == "ion3") { |
| return WM_ION3; |
| } |
| if (name == "KWin") { |
| return WM_KWIN; |
| } |
| if (name == "matchbox") { |
| return WM_MATCHBOX; |
| } |
| if (name == "Metacity") { |
| return WM_METACITY; |
| } |
| if (name == "Mutter (Muffin)") { |
| return WM_MUFFIN; |
| } |
| if (name == "GNOME Shell") { |
| return WM_MUTTER; // GNOME Shell uses Mutter |
| } |
| if (name == "Mutter") { |
| return WM_MUTTER; |
| } |
| if (name == "notion") { |
| return WM_NOTION; |
| } |
| if (name == "Openbox") { |
| return WM_OPENBOX; |
| } |
| if (name == "qtile") { |
| return WM_QTILE; |
| } |
| if (name == "ratpoison") { |
| return WM_RATPOISON; |
| } |
| if (name == "stumpwm") { |
| return WM_STUMPWM; |
| } |
| if (name == "wmii") { |
| return WM_WMII; |
| } |
| if (name == "Xfwm4") { |
| return WM_XFWM4; |
| } |
| if (name == "xmonad") { |
| return WM_XMONAD; |
| } |
| return WM_OTHER; |
| } |
| |
| std::string GuessWindowManagerName() { |
| std::string name = x11::Connection::Get()->GetWmName(); |
| return name.empty() ? "Unknown" : name; |
| } |
| |
| UMALinuxWindowManager GetWindowManagerUMA() { |
| switch (GuessWindowManager()) { |
| case WM_OTHER: |
| return UMALinuxWindowManager::kOther; |
| case WM_UNNAMED: |
| return UMALinuxWindowManager::kUnnamed; |
| case WM_AWESOME: |
| return UMALinuxWindowManager::kAwesome; |
| case WM_BLACKBOX: |
| return UMALinuxWindowManager::kBlackbox; |
| case WM_COMPIZ: |
| return UMALinuxWindowManager::kCompiz; |
| case WM_ENLIGHTENMENT: |
| return UMALinuxWindowManager::kEnlightenment; |
| case WM_FLUXBOX: |
| return UMALinuxWindowManager::kFluxbox; |
| case WM_I3: |
| return UMALinuxWindowManager::kI3; |
| case WM_ICE_WM: |
| return UMALinuxWindowManager::kIceWM; |
| case WM_ION3: |
| return UMALinuxWindowManager::kIon3; |
| case WM_KWIN: |
| return UMALinuxWindowManager::kKWin; |
| case WM_MATCHBOX: |
| return UMALinuxWindowManager::kMatchbox; |
| case WM_METACITY: |
| return UMALinuxWindowManager::kMetacity; |
| case WM_MUFFIN: |
| return UMALinuxWindowManager::kMuffin; |
| case WM_MUTTER: |
| return UMALinuxWindowManager::kMutter; |
| case WM_NOTION: |
| return UMALinuxWindowManager::kNotion; |
| case WM_OPENBOX: |
| return UMALinuxWindowManager::kOpenbox; |
| case WM_QTILE: |
| return UMALinuxWindowManager::kQtile; |
| case WM_RATPOISON: |
| return UMALinuxWindowManager::kRatpoison; |
| case WM_STUMPWM: |
| return UMALinuxWindowManager::kStumpWM; |
| case WM_WMII: |
| return UMALinuxWindowManager::kWmii; |
| case WM_XFWM4: |
| return UMALinuxWindowManager::kXfwm4; |
| case WM_XMONAD: |
| return UMALinuxWindowManager::kXmonad; |
| } |
| NOTREACHED(); |
| } |
| |
| bool IsX11WindowFullScreen(x11::Window window) { |
| // If _NET_WM_STATE_FULLSCREEN is in _NET_SUPPORTED, use the presence or |
| // absence of _NET_WM_STATE_FULLSCREEN in _NET_WM_STATE to determine |
| // whether we're fullscreen. |
| x11::Atom fullscreen_atom = x11::GetAtom("_NET_WM_STATE_FULLSCREEN"); |
| if (x11::Connection::Get()->WmSupportsHint(fullscreen_atom)) { |
| std::vector<x11::Atom> atom_properties; |
| if (x11::Connection::Get()->GetArrayProperty( |
| window, x11::GetAtom("_NET_WM_STATE"), &atom_properties)) { |
| return base::Contains(atom_properties, fullscreen_atom); |
| } |
| } |
| |
| auto* connection = x11::Connection::Get(); |
| gfx::Rect window_rect; |
| if (auto geometry = connection->GetGeometry(window).Sync()) { |
| window_rect = |
| gfx::Rect(geometry->x, geometry->y, geometry->width, geometry->height); |
| } else { |
| return false; |
| } |
| |
| // TODO(thomasanderson): We should use |
| // display::Screen::GetDisplayNearestWindow() instead of using the |
| // connection screen size, which encompasses all displays. |
| int width = connection->default_screen().width_in_pixels; |
| int height = connection->default_screen().height_in_pixels; |
| return window_rect.size() == gfx::Size(width, height); |
| } |
| |
| bool SuspendX11ScreenSaver(bool suspend) { |
| static const bool kScreenSaverAvailable = IsX11ScreenSaverAvailable(); |
| if (!kScreenSaverAvailable) { |
| return false; |
| } |
| |
| x11::Connection::Get()->screensaver().Suspend({suspend}); |
| return true; |
| } |
| |
| SkColorType ColorTypeForVisual(x11::VisualId visual) { |
| struct { |
| SkColorType color_type; |
| unsigned long red_mask; |
| unsigned long green_mask; |
| unsigned long blue_mask; |
| int bpp; |
| } color_infos[] = { |
| {kRGB_565_SkColorType, 0xf800, 0x7e0, 0x1f, 16}, |
| {kARGB_4444_SkColorType, 0xf000, 0xf00, 0xf0, 16}, |
| {kRGBA_8888_SkColorType, 0xff, 0xff00, 0xff0000, 32}, |
| {kBGRA_8888_SkColorType, 0xff0000, 0xff00, 0xff, 32}, |
| {kRGBA_1010102_SkColorType, 0x3ff, 0xffc00, 0x3ff00000, 32}, |
| {kBGRA_1010102_SkColorType, 0x3ff00000, 0xffc00, 0x3ff, 32}, |
| }; |
| auto* connection = x11::Connection::Get(); |
| const auto* vis = connection->GetVisualInfoFromId(visual); |
| if (!vis) { |
| return kUnknown_SkColorType; |
| } |
| // We don't currently support anything other than TrueColor and DirectColor. |
| if (!vis->visual_type->red_mask || !vis->visual_type->green_mask || |
| !vis->visual_type->blue_mask) { |
| return kUnknown_SkColorType; |
| } |
| for (const auto& color_info : color_infos) { |
| if (vis->visual_type->red_mask == color_info.red_mask && |
| vis->visual_type->green_mask == color_info.green_mask && |
| vis->visual_type->blue_mask == color_info.blue_mask && |
| vis->format->bits_per_pixel == color_info.bpp) { |
| return color_info.color_type; |
| } |
| } |
| LOG(ERROR) << "Unsupported visual with rgb mask 0x" << std::hex |
| << vis->visual_type->red_mask << ", 0x" |
| << vis->visual_type->green_mask << ", 0x" |
| << vis->visual_type->blue_mask |
| << ". Please report this to https://crbug.com/1025266"; |
| return kUnknown_SkColorType; |
| } |
| |
| x11::Future<void> SendClientMessage(x11::Window window, |
| x11::Window target, |
| x11::Atom type, |
| const std::array<uint32_t, 5> data, |
| x11::EventMask event_mask) { |
| x11::ClientMessageEvent event{.format = 32, .window = window, .type = type}; |
| event.data.data32 = data; |
| return x11::Connection::Get()->SendEvent(event, target, event_mask); |
| } |
| |
| bool IsVulkanSurfaceSupported() { |
| static const char* extensions[] = { |
| "DRI3", // open source driver. |
| "ATIFGLRXDRI", // AMD proprietary driver. |
| "NV-CONTROL", // NVidia proprietary driver. |
| }; |
| auto* connection = x11::Connection::Get(); |
| for (const auto* extension : extensions) { |
| if (connection->QueryExtension(extension).Sync()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| gfx::ImageSkia GetNativeWindowIcon(intptr_t target_window_id) { |
| std::vector<uint32_t> data; |
| if (!x11::Connection::Get()->GetArrayProperty( |
| static_cast<x11::Window>(target_window_id), |
| x11::GetAtom("_NET_WM_ICON"), &data)) { |
| return gfx::ImageSkia(); |
| } |
| |
| // The format of |data| is concatenation of sections like |
| // [width, height, pixel data of size width * height], and the total bytes |
| // number of |data| is |size|. And here we are picking the largest icon. |
| int width = 0; |
| int height = 0; |
| int start = 0; |
| size_t i = 0; |
| while (i + 1 < data.size()) { |
| if ((static_cast<int>(data[i] * data[i + 1]) > width * height) && |
| (i + 1 + data[i] * data[i + 1] < data.size())) { |
| width = static_cast<int>(data[i]); |
| height = static_cast<int>(data[i + 1]); |
| start = i + 2; |
| } |
| i += 2 + static_cast<int>(data[i] * data[i + 1]); |
| } |
| |
| if (width == 0 || height == 0) { |
| return gfx::ImageSkia(); |
| } |
| |
| SkBitmap result; |
| SkImageInfo info = SkImageInfo::MakeN32(width, height, kUnpremul_SkAlphaType); |
| result.allocPixels(info); |
| |
| uint32_t* pixels_data = reinterpret_cast<uint32_t*>(result.getPixels()); |
| |
| for (long y = 0; y < height; ++y) { |
| for (long x = 0; x < width; ++x) { |
| UNSAFE_TODO(pixels_data[result.rowBytesAsPixels() * y + x]) = |
| static_cast<uint32_t>(data[start + width * y + x]); |
| } |
| } |
| |
| return gfx::ImageSkia::CreateFrom1xBitmap(result); |
| } |
| |
| } // namespace ui |