diff --git a/src/backends/metal/video.rs b/src/backends/metal/video.rs index 668c1460..efd447b7 100644 --- a/src/backends/metal/video.rs +++ b/src/backends/metal/video.rs @@ -1,4 +1,5 @@ mod copy_device; +mod discovery; mod hardware_cursor; mod lease; mod model; @@ -18,19 +19,22 @@ pub use { properties::{DefaultProperty, TypedProperty}, }; -use properties::{ - DefaultValue, collect_properties, collect_untyped_properties, create_default_properties, +use { + discovery::{ + create_connector, create_connector_display_data, create_crtc, create_encoder, create_plane, + get_connectors, + }, + properties::collect_untyped_properties, }; use { crate::{ - async_engine::Phase, backend::{ BackendColorSpace, BackendConnectorState, BackendDrmDevice, BackendDrmLessee, BackendEotfs, BackendEvent, BackendGammaLut, BackendGammaLutElement, - BackendLuminance, CONCAP_CONNECTOR, CONCAP_MODE_SETTING, CONCAP_PHYSICAL_DISPLAY, + CONCAP_CONNECTOR, CONCAP_MODE_SETTING, CONCAP_PHYSICAL_DISPLAY, Connector, ConnectorCaps, ConnectorEvent, ConnectorId, ConnectorKernelId, DrmDeviceId, - Mode, MonitorInfo, OutputId, + MonitorInfo, transaction::{ BackendConnectorTransaction, BackendConnectorTransactionError, BackendConnectorTransactionType, BackendConnectorTransactionTypeDyn, @@ -38,42 +42,33 @@ use { }, backends::metal::{ MetalBackend, MetalError, - present::{ - DEFAULT_POST_COMMIT_MARGIN, DEFAULT_PRE_COMMIT_MARGIN, POST_COMMIT_MARGIN_DELTA, - }, - transaction::{DrmConnectorState, DrmCrtcState, DrmPlaneState, MetalDeviceTransaction}, + present::{DEFAULT_POST_COMMIT_MARGIN, POST_COMMIT_MARGIN_DELTA}, + transaction::MetalDeviceTransaction, }, - cmm::cmm_primaries::Primaries, drm_feedback::DrmFeedback, - edid::{CtaDataBlock, Descriptor, EdidExtension}, format::XRGB8888, gfx_api::GfxApi, ifs::wp_presentation_feedback::{KIND_HW_COMPLETION, KIND_VSYNC, KIND_ZERO_COPY}, tree::OutputNode, udev::UdevDevice, utils::{ - binary_search_map::BinarySearchMap, bitflags::BitflagsExt, cell_ext::CellExt, - clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, - geometric_decay::GeometricDecay, numcell::NumCell, ordered_float::F64, - oserror::OsError, + cell_ext::CellExt, clonecell::CloneCell, copyhashmap::CopyHashMap, + errorfmt::ErrorFmt, oserror::OsError, }, video::{ - INVALID_MODIFIER, drm::{ - ConnectorStatus, ConnectorType, DRM_CLIENT_CAP_ATOMIC, DrmBlob, DrmCardResources, - DrmConnector, DrmCrtc, DrmEncoder, DrmError, DrmEvent, DrmFb, DrmMaster, - DrmObject, DrmPlane, DrmProperty, DrmPropertyType, DrmVersion, - HDMI_EOTF_TRADITIONAL_GAMMA_SDR, drm_mode_modeinfo, hdr_output_metadata, + ConnectorStatus, DRM_CLIENT_CAP_ATOMIC, DrmBlob, DrmCardResources, DrmConnector, + DrmCrtc, DrmError, DrmEvent, DrmFb, DrmMaster, DrmObject, DrmProperty, + DrmVersion, drm_mode_modeinfo, hdr_output_metadata, }, gbm::GbmDevice, }, }, ahash::{AHashMap, AHashSet}, bstr::ByteSlice, - indexmap::indexset, isnt::std_1::collections::IsntHashMapExt, std::{ - cell::{Cell, RefCell}, + cell::Cell, collections::hash_map::Entry, mem, ops::DerefMut, @@ -555,626 +550,6 @@ impl Connector for MetalConnector { } } -fn get_connectors( - backend: &Rc, - dev: &Rc, - ids: &[DrmConnector], -) -> Result< - ( - CopyHashMap>, - CopyHashMap, - ), - DrmError, -> { - let connectors = CopyHashMap::new(); - let futures = CopyHashMap::new(); - for connector in ids { - match create_connector(backend, *connector, dev) { - Ok((con, fut)) => { - let id = con.id; - connectors.set(id, con); - futures.set(id, fut); - } - Err(e) => return Err(DrmError::CreateConnector(Box::new(e))), - } - } - Ok((connectors, futures)) -} - - -fn create_connector( - backend: &Rc, - connector: DrmConnector, - dev: &Rc, -) -> Result<(Rc, ConnectorFutures), DrmError> { - let display = create_connector_display_data(connector, dev)?; - log::info!( - "Creating connector {} for device {}", - display.connector_id, - dev.devnode.as_bytes().as_bstr(), - ); - let slf = Rc::new(MetalConnector { - id: connector, - kernel_id: Cell::new(display.connector_id), - master: dev.master.clone(), - state: backend.state.clone(), - dev: dev.clone(), - backend: backend.clone(), - connector_id: backend.state.connector_ids.next(), - buffers: Default::default(), - color_description: CloneCell::new(backend.state.color_manager.srgb_gamma22().clone()), - lease: Cell::new(None), - buffers_idle: Cell::new(true), - crtc_idle: Cell::new(true), - has_damage: NumCell::new(1), - primary_plane: Default::default(), - cursor_plane: Default::default(), - crtc: Default::default(), - on_change: Default::default(), - present_trigger: Default::default(), - cursor_x: Cell::new(0), - cursor_y: Cell::new(0), - cursor_enabled: Cell::new(false), - cursor_buffers: Default::default(), - display: RefCell::new(display), - frontend_state: Cell::new(FrontState::Removed), - cursor_changed: Cell::new(false), - cursor_damage: Cell::new(false), - cursor_swap_buffer: Cell::new(false), - cursor_sync: Default::default(), - drm_feedback: Default::default(), - scanout_buffers: Default::default(), - active_framebuffer: Default::default(), - next_framebuffer: Default::default(), - direct_scanout_active: Cell::new(false), - next_vblank_nsec: Cell::new(0), - version: Default::default(), - expected_sequence: Default::default(), - pre_commit_margin_decay: GeometricDecay::new(0.5, DEFAULT_PRE_COMMIT_MARGIN), - pre_commit_margin: Cell::new(DEFAULT_PRE_COMMIT_MARGIN), - post_commit_margin_decay: GeometricDecay::new(0.1, dev.min_post_commit_margin.get()), - post_commit_margin: Cell::new(dev.min_post_commit_margin.get()), - vblank_miss_sec: Cell::new(0), - vblank_miss_this_sec: Default::default(), - presentation_is_sync: Cell::new(false), - presentation_is_zero_copy: Cell::new(false), - }); - let futures = ConnectorFutures { - _present: backend.state.eng.spawn2( - "present loop", - Phase::Present, - slf.clone().present_loop(), - ), - }; - Ok((slf, futures)) -} - -fn create_connector_display_data( - connector: DrmConnector, - dev: &Rc, -) -> Result { - let info = dev.master.get_connector_info(connector, true)?; - let mut crtcs = BinarySearchMap::new(); - for encoder in info.encoders { - if let Some(encoder) = dev.encoders.get(&encoder) { - for (_, crtc) in &encoder.crtcs { - crtcs.insert(crtc.id, crtc.clone()); - } - } - } - let props = collect_properties(&dev.master, connector)?; - let connection = ConnectorStatus::from_drm(info.connection); - let mut name = String::new(); - let mut manufacturer = String::new(); - let mut serial_number = String::new(); - let mut vrr_refresh_max_nsec = u64::MAX; - let connector_id = ConnectorKernelId { - ty: ConnectorType::from_drm(info.connector_type), - idx: info.connector_type_id, - }; - let mut supports_bt2020 = false; - let mut supports_pq = false; - let mut luminance = None; - let mut primaries = Primaries::SRGB; - 'fetch_edid: { - if connection != ConnectorStatus::Connected { - break 'fetch_edid; - } - let edid = match props.get("EDID") { - Ok(e) => e, - _ => { - log::warn!( - "Connector {} is connected but has no EDID blob", - connector_id, - ); - break 'fetch_edid; - } - }; - let blob = match dev.master.getblob_vec::(DrmBlob(edid.value as _)) { - Ok(b) => b, - Err(e) => { - log::error!( - "Could not fetch edid property of connector {}: {}", - connector_id, - ErrorFmt(e) - ); - break 'fetch_edid; - } - }; - let edid = match crate::edid::parse(&blob) { - Ok(e) => e, - Err(e) => { - log::error!( - "Could not parse edid property of connector {}: {}", - connector_id, - ErrorFmt(e) - ); - break 'fetch_edid; - } - }; - manufacturer = edid.base_block.id_manufacturer_name.to_string(); - for descriptor in edid.base_block.descriptors.iter().flatten() { - match descriptor { - Descriptor::DisplayProductSerialNumber(s) => { - serial_number.clone_from(s); - } - Descriptor::DisplayProductName(s) => { - name.clone_from(s); - } - _ => {} - } - } - if name.is_empty() { - log::warn!( - "The display attached to connector {} does not have a product name descriptor", - connector_id, - ); - } - if serial_number.is_empty() { - log::warn!( - "The display attached to connector {} does not have a serial number descriptor", - connector_id, - ); - serial_number = edid.base_block.id_serial_number.to_string(); - } - let min_vrr_hz = 'fetch_min_hz: { - for ext in &edid.extension_blocks { - if let EdidExtension::CtaV3(cta) = ext { - for data_block in &cta.data_blocks { - if let CtaDataBlock::VendorAmd(amd) = data_block { - break 'fetch_min_hz amd.minimum_refresh_hz as u64; - } - } - } - } - for desc in &edid.base_block.descriptors { - if let Some(desc) = desc - && let Descriptor::DisplayRangeLimitsAndAdditionalTiming(timings) = desc - { - break 'fetch_min_hz timings.vertical_field_rate_min as u64; - } - } - 0 - }; - if min_vrr_hz > 0 { - vrr_refresh_max_nsec = 1_000_000_000 / min_vrr_hz; - } - let cc = &edid.base_block.chromaticity_coordinates; - let map = |c: u16| F64(c as f64 / 1024.0); - primaries = Primaries { - r: (map(cc.red_x), map(cc.red_y)), - g: (map(cc.green_x), map(cc.green_y)), - b: (map(cc.blue_x), map(cc.blue_y)), - wp: (map(cc.white_x), map(cc.white_y)), - }; - for ext in &edid.extension_blocks { - if let EdidExtension::CtaV3(cta) = ext { - for data_block in &cta.data_blocks { - match data_block { - CtaDataBlock::Colorimetry(c) => { - if c.bt2020_rgb { - supports_bt2020 = true; - } - } - CtaDataBlock::StaticHdrMetadata(h) => { - if h.smpte_st_2084 { - supports_pq = true; - } - if let Some(max) = h.max_luminance { - luminance = Some(BackendLuminance { - min: h.min_luminance.unwrap_or(0.0), - max, - max_fall: h.max_luminance.unwrap_or(max), - }); - } - } - _ => {} - } - } - } - } - } - let output_id = OutputId::new(connector_id.to_string(), manufacturer, name, serial_number); - let first_mode = info - .modes - .first() - .cloned() - .map(|m| m.to_backend()) - .unwrap_or_default(); - let persistent = match dev.backend.persistent_display_data.get(&output_id) { - Some(ds) => { - if connection != ConnectorStatus::Disconnected { - log::info!("Reusing desired state for {:?}", output_id); - } - ds - } - None => { - let ds = Rc::new(PersistentDisplayData { - state: RefCell::new(BackendConnectorState { - serial: dev.backend.state.backend_connector_state_serials.next(), - enabled: true, - active: true, - mode: first_mode, - non_desktop_override: None, - vrr: false, - tearing: false, - format: XRGB8888, - color_space: Default::default(), - eotf: Default::default(), - gamma_lut: Default::default(), - }), - }); - dev.backend - .persistent_display_data - .set(output_id.clone(), ds.clone()); - ds - } - }; - let mut desired_state = persistent.state.borrow_mut(); - if desired_state.mode == Mode::default() { - desired_state.mode = first_mode; - } else if info - .modes - .iter() - .all(|m| m.to_backend() != desired_state.mode) - { - log::warn!("Discarding previously desired mode"); - desired_state.mode = first_mode; - } - let non_desktop = props.get("non-desktop")?.value != 0; - let vrr_capable = match props.get("vrr_capable") { - Ok(c) => c.value == 1, - Err(_) => false, - }; - if !vrr_capable && desired_state.vrr { - log::warn!("Connector has lost VRR capability"); - desired_state.vrr = false; - } - { - let viable = match desired_state.eotf { - BackendEotfs::Default => true, - BackendEotfs::Pq => supports_pq, - }; - if !viable { - log::warn!("Discarding previously desired EOTF"); - desired_state.eotf = BackendEotfs::Default; - } - } - { - let viable = match desired_state.color_space { - BackendColorSpace::Default => true, - BackendColorSpace::Bt2020 => supports_bt2020, - }; - if !viable { - log::warn!("Discarding previously desired color space"); - desired_state.color_space = BackendColorSpace::Default; - } - } - drop(desired_state); - let default_properties = create_default_properties( - &props, - &[ - ("Broadcast RGB", DefaultValue::Enum("Automatic")), - ("HDR_SOURCE_METADATA", DefaultValue::Fixed(0)), - ("Output format", DefaultValue::Enum("Default")), - ("WRITEBACK_FB_ID", DefaultValue::Fixed(0)), - ("WRITEBACK_OUT_FENCE_PTR", DefaultValue::Fixed(0)), - ("content type", DefaultValue::Enum("No Data")), - ("dither", DefaultValue::Enum("off")), - ("max bpc", DefaultValue::RangeMax), - ], - ); - let hdr_metadata_prop = props - .get("HDR_OUTPUT_METADATA") - .map(|p| p.map(|v| DrmBlob(v as _))) - .ok(); - let mut hdr_metadata = None; - let mut hdr_metadata_blob_id = DrmBlob::NONE; - if let Some(p) = &hdr_metadata_prop { - hdr_metadata_blob_id = p.value; - hdr_metadata = Some(hdr_output_metadata::from_eotf( - HDMI_EOTF_TRADITIONAL_GAMMA_SDR, - )); - if p.value.is_some() { - match dev.master.getblob::(p.value) { - Ok(m) => hdr_metadata = Some(m), - _ => { - log::debug!("Could not retrieve hdr output metadata"); - } - } - } - } - let colorspace_prop = props.get("Colorspace").ok(); - let crtc_id = props.get("CRTC_ID")?.map(|v| DrmCrtc(v as _)); - let drm_state = DrmConnectorState { - crtc_id: crtc_id.value, - color_space: colorspace_prop.map(|p| p.value), - hdr_metadata, - hdr_metadata_blob_id, - hdr_metadata_blob: None, - locked: true, - fb: DrmFb::NONE, - fb_idx: 0, - cursor_fb: DrmFb::NONE, - cursor_fb_idx: 0, - cursor_x: 0, - cursor_y: 0, - out_fd: None, - src_w: 0, - src_h: 0, - crtc_x: 0, - crtc_y: 0, - crtc_w: 0, - crtc_h: 0, - }; - Ok(ConnectorDisplayData { - crtc_id: props.get("CRTC_ID")?.id, - crtcs, - first_mode, - modes: info.modes, - persistent, - refresh: 0, - non_desktop, - non_desktop_effective: non_desktop, - vrr_capable, - _vrr_refresh_max_nsec: vrr_refresh_max_nsec, - default_properties, - untyped_properties: props.to_untyped(), - connection, - mm_width: info.mm_width, - mm_height: info.mm_height, - _subpixel: info.subpixel, - supports_bt2020, - supports_pq, - primaries, - luminance, - connector_id, - output_id, - colorspace: colorspace_prop.map(|p| p.id), - hdr_metadata: hdr_metadata_prop.map(|p| p.id), - drm_state, - }) -} - -fn create_encoder( - encoder: DrmEncoder, - master: &Rc, - crtcs: &AHashMap>, -) -> Result { - let info = master.get_encoder_info(encoder)?; - let mut possible = AHashMap::new(); - for crtc in crtcs.values() { - if info.possible_crtcs.contains(1 << crtc.idx) { - possible.insert(crtc.id, crtc.clone()); - } - } - Ok(MetalEncoder { - id: encoder, - crtcs: possible, - }) -} - -fn create_crtc( - crtc: DrmCrtc, - idx: usize, - master: &Rc, - planes: &AHashMap>, -) -> Result { - let mask = 1 << idx; - let mut possible_planes = BinarySearchMap::new(); - for plane in planes.values() { - if plane.possible_crtcs.contains(mask) { - possible_planes.insert(plane.id, plane.clone()); - } - } - let props = collect_properties(master, crtc)?; - let default_properties = create_default_properties( - &props, - &[ - ("AMD_CRTC_REGAMMA_TF", DefaultValue::Enum("Default")), - ("CTM", DefaultValue::Fixed(0)), - ("DEGAMMA_LUT", DefaultValue::Fixed(0)), - ("OUT_FENCE_PTR", DefaultValue::Fixed(0)), - ], - ); - let active = props.get("ACTIVE")?.map(|v| v == 1); - let mode_id = props.get("MODE_ID")?.map(|v| DrmBlob(v as u32)); - let vrr_enabled = props.get("VRR_ENABLED")?.map(|v| v == 1); - let out_fence_ptr = props.get("OUT_FENCE_PTR")?; - let gamma_lut = props - .get("GAMMA_LUT") - .ok() - .map(|v| v.map(|v| DrmBlob(v as u32))); - let mut gamma_lut_size = None; - if gamma_lut.is_some() { - gamma_lut_size = props.get("GAMMA_LUT_SIZE").ok().map(|v| v.value as u32); - } - let mut mode = None; - if mode_id.value.is_some() { - match master.getblob::(mode_id.value) { - Ok(m) => mode = Some(m.into()), - _ => { - log::debug!("Could not retrieve current mode of connector"); - } - } - } - let state = DrmCrtcState { - active: active.value, - mode, - mode_blob_id: mode_id.value, - mode_blob: None, - vrr_enabled: vrr_enabled.value, - assigned_connector: DrmConnector::NONE, - gamma_lut: None, - gamma_lut_blob_id: gamma_lut.map_or(DrmBlob::NONE, |v| v.value), - gamma_lut_blob: None, - }; - Ok(MetalCrtc { - id: crtc, - idx, - master: master.clone(), - default_properties, - untyped_properties: RefCell::new(props.to_untyped()), - lease: Cell::new(None), - possible_planes, - connector: Default::default(), - pending_flip: Default::default(), - drm_state: RefCell::new(state), - active: active.id, - mode_id: mode_id.id, - vrr_enabled: vrr_enabled.id, - out_fence_ptr: out_fence_ptr.id, - gamma_lut: gamma_lut.map(|v| v.id), - gamma_lut_size, - sequence: Cell::new(0), - have_queued_sequence: Cell::new(false), - needs_vblank_emulation: Cell::new(false), - }) -} - -fn create_plane(plane: DrmPlane, master: &Rc) -> Result { - let info = master.get_plane_info(plane)?; - let props = collect_properties(master, plane)?; - let mut formats = AHashMap::new(); - if let Some((_, v)) = props.props.get(b"IN_FORMATS".as_bstr()) { - for format in master.get_in_formats(*v as _)? { - if format.modifiers.is_empty() { - continue; - } - if let Some(f) = crate::format::formats().get(&format.format) { - formats.insert( - format.format, - PlaneFormat { - format: f, - modifiers: format.modifiers, - }, - ); - } - } - } else { - for format in info.format_types { - if let Some(f) = crate::format::formats().get(&format) { - formats.insert( - format, - PlaneFormat { - format: f, - modifiers: indexset![INVALID_MODIFIER], - }, - ); - } - } - } - let ty = match props.props.get(b"type".as_bstr()) { - Some((def, val)) => match &def.ty { - DrmPropertyType::Enum { values, .. } => 'ty: { - for v in values { - if v.value == *val { - match v.name.as_bytes() { - b"Overlay" => break 'ty PlaneType::Overlay, - b"Primary" => break 'ty PlaneType::Primary, - b"Cursor" => break 'ty PlaneType::Cursor, - _ => return Err(DrmError::UnknownPlaneType(v.name.to_owned())), - } - } - } - return Err(DrmError::InvalidPlaneType(*val)); - } - _ => return Err(DrmError::InvalidPlaneTypeProperty), - }, - _ => { - return Err(DrmError::MissingProperty( - "type".to_string().into_boxed_str(), - )); - } - }; - let default_properties = create_default_properties( - &props, - &[ - ("AMD_PLANE_BLEND_LUT", DefaultValue::Fixed(0)), - ("AMD_PLANE_BLEND_TF", DefaultValue::Enum("Default")), - ("AMD_PLANE_CTM", DefaultValue::Fixed(0)), - ("AMD_PLANE_DEGAMMA_LUT", DefaultValue::Fixed(0)), - ("AMD_PLANE_HDR_MULT", DefaultValue::Fixed(0)), - ("AMD_PLANE_LUT3D", DefaultValue::Fixed(0)), - ("AMD_PLANE_SHAPER_LUT", DefaultValue::Fixed(0)), - ("AMD_PLANE_SHAPER_TF", DefaultValue::Enum("Default")), - ("alpha", DefaultValue::RangeMax), - ("pixel blend mode", DefaultValue::Enum("Pre-multiplied")), - ("rotation", DefaultValue::Bitmask(&["rotate-0"])), - ], - ); - let fb_id = props.get("FB_ID")?.map(|v| DrmFb(v as _)); - let crtc_id = props.get("CRTC_ID")?.map(|v| DrmCrtc(v as _)); - let crtc_x = props.get("CRTC_X")?.map(|v| v as i32); - let crtc_y = props.get("CRTC_Y")?.map(|v| v as i32); - let crtc_w = props.get("CRTC_W")?.map(|v| v as i32); - let crtc_h = props.get("CRTC_H")?.map(|v| v as i32); - let src_x = props.get("SRC_X")?.map(|v| v as u32); - let src_y = props.get("SRC_Y")?.map(|v| v as u32); - let src_w = props.get("SRC_W")?.map(|v| v as u32); - let src_h = props.get("SRC_H")?.map(|v| v as u32); - let in_fence_fd = props.get("IN_FENCE_FD")?; - let state = DrmPlaneState { - fb_id: fb_id.value, - src_x: src_x.value, - src_y: src_y.value, - src_w: src_w.value, - src_h: src_h.value, - assigned_crtc: DrmCrtc::NONE, - crtc_id: crtc_id.value, - crtc_x: crtc_x.value, - crtc_y: crtc_y.value, - crtc_w: crtc_w.value, - crtc_h: crtc_h.value, - buffers: None, - }; - Ok(MetalPlane { - id: plane, - master: master.clone(), - default_properties, - untyped_properties: RefCell::new(props.to_untyped()), - ty, - possible_crtcs: info.possible_crtcs, - formats, - drm_state: RefCell::new(state), - fb_id: fb_id.id, - crtc_id: crtc_id.id, - crtc_x: crtc_x.id, - crtc_y: crtc_y.id, - crtc_w: crtc_w.id, - crtc_h: crtc_h.id, - src_x: src_x.id, - src_y: src_y.id, - src_w: src_w.id, - src_h: src_h.id, - in_fence_fd: in_fence_fd.id, - mode_w: Cell::new(0), - mode_h: Cell::new(0), - lease: Cell::new(None), - }) -} - - impl MetalBackend { pub fn check_render_context(&self, dev: &Rc) -> bool { let ctx = match self.ctx.get() { diff --git a/src/backends/metal/video/discovery.rs b/src/backends/metal/video/discovery.rs new file mode 100644 index 00000000..58a9a152 --- /dev/null +++ b/src/backends/metal/video/discovery.rs @@ -0,0 +1,660 @@ +use { + super::{ + ConnectorDisplayData, ConnectorFutures, FrontState, MetalConnector, MetalCrtc, + MetalDrmDevice, MetalEncoder, MetalPlane, PersistentDisplayData, PlaneFormat, PlaneType, + properties::{DefaultValue, collect_properties, create_default_properties}, + }, + crate::{ + async_engine::Phase, + backend::{ + BackendColorSpace, BackendConnectorState, BackendEotfs, BackendLuminance, + ConnectorKernelId, Mode, OutputId, + }, + backends::metal::{ + MetalBackend, + present::DEFAULT_PRE_COMMIT_MARGIN, + transaction::{DrmConnectorState, DrmCrtcState, DrmPlaneState}, + }, + cmm::cmm_primaries::Primaries, + edid::{CtaDataBlock, Descriptor, EdidExtension}, + format::XRGB8888, + utils::{ + binary_search_map::BinarySearchMap, bitflags::BitflagsExt, clonecell::CloneCell, + copyhashmap::CopyHashMap, errorfmt::ErrorFmt, geometric_decay::GeometricDecay, + numcell::NumCell, ordered_float::F64, + }, + video::{ + INVALID_MODIFIER, + drm::{ + ConnectorStatus, ConnectorType, DrmBlob, DrmConnector, DrmCrtc, DrmEncoder, + DrmError, DrmFb, DrmMaster, DrmObject, DrmPlane, DrmPropertyType, + HDMI_EOTF_TRADITIONAL_GAMMA_SDR, drm_mode_modeinfo, hdr_output_metadata, + }, + }, + }, + ahash::AHashMap, + bstr::ByteSlice, + indexmap::indexset, + std::{ + cell::{Cell, RefCell}, + rc::Rc, + }, +}; + +pub(super) fn get_connectors( + backend: &Rc, + dev: &Rc, + ids: &[DrmConnector], +) -> Result< + ( + CopyHashMap>, + CopyHashMap, + ), + DrmError, +> { + let connectors = CopyHashMap::new(); + let futures = CopyHashMap::new(); + for connector in ids { + match create_connector(backend, *connector, dev) { + Ok((con, fut)) => { + let id = con.id; + connectors.set(id, con); + futures.set(id, fut); + } + Err(e) => return Err(DrmError::CreateConnector(Box::new(e))), + } + } + Ok((connectors, futures)) +} + +pub(super) fn create_connector( + backend: &Rc, + connector: DrmConnector, + dev: &Rc, +) -> Result<(Rc, ConnectorFutures), DrmError> { + let display = create_connector_display_data(connector, dev)?; + log::info!( + "Creating connector {} for device {}", + display.connector_id, + dev.devnode.as_bytes().as_bstr(), + ); + let slf = Rc::new(MetalConnector { + id: connector, + kernel_id: Cell::new(display.connector_id), + master: dev.master.clone(), + state: backend.state.clone(), + dev: dev.clone(), + backend: backend.clone(), + connector_id: backend.state.connector_ids.next(), + buffers: Default::default(), + color_description: CloneCell::new(backend.state.color_manager.srgb_gamma22().clone()), + lease: Cell::new(None), + buffers_idle: Cell::new(true), + crtc_idle: Cell::new(true), + has_damage: NumCell::new(1), + primary_plane: Default::default(), + cursor_plane: Default::default(), + crtc: Default::default(), + on_change: Default::default(), + present_trigger: Default::default(), + cursor_x: Cell::new(0), + cursor_y: Cell::new(0), + cursor_enabled: Cell::new(false), + cursor_buffers: Default::default(), + display: RefCell::new(display), + frontend_state: Cell::new(FrontState::Removed), + cursor_changed: Cell::new(false), + cursor_damage: Cell::new(false), + cursor_swap_buffer: Cell::new(false), + cursor_sync: Default::default(), + drm_feedback: Default::default(), + scanout_buffers: Default::default(), + active_framebuffer: Default::default(), + next_framebuffer: Default::default(), + direct_scanout_active: Cell::new(false), + next_vblank_nsec: Cell::new(0), + version: Default::default(), + expected_sequence: Default::default(), + pre_commit_margin_decay: GeometricDecay::new(0.5, DEFAULT_PRE_COMMIT_MARGIN), + pre_commit_margin: Cell::new(DEFAULT_PRE_COMMIT_MARGIN), + post_commit_margin_decay: GeometricDecay::new(0.1, dev.min_post_commit_margin.get()), + post_commit_margin: Cell::new(dev.min_post_commit_margin.get()), + vblank_miss_sec: Cell::new(0), + vblank_miss_this_sec: Default::default(), + presentation_is_sync: Cell::new(false), + presentation_is_zero_copy: Cell::new(false), + }); + let futures = ConnectorFutures { + _present: backend.state.eng.spawn2( + "present loop", + Phase::Present, + slf.clone().present_loop(), + ), + }; + Ok((slf, futures)) +} + +pub(super) fn create_connector_display_data( + connector: DrmConnector, + dev: &Rc, +) -> Result { + let info = dev.master.get_connector_info(connector, true)?; + let mut crtcs = BinarySearchMap::new(); + for encoder in info.encoders { + if let Some(encoder) = dev.encoders.get(&encoder) { + for (_, crtc) in &encoder.crtcs { + crtcs.insert(crtc.id, crtc.clone()); + } + } + } + let props = collect_properties(&dev.master, connector)?; + let connection = ConnectorStatus::from_drm(info.connection); + let mut name = String::new(); + let mut manufacturer = String::new(); + let mut serial_number = String::new(); + let mut vrr_refresh_max_nsec = u64::MAX; + let connector_id = ConnectorKernelId { + ty: ConnectorType::from_drm(info.connector_type), + idx: info.connector_type_id, + }; + let mut supports_bt2020 = false; + let mut supports_pq = false; + let mut luminance = None; + let mut primaries = Primaries::SRGB; + 'fetch_edid: { + if connection != ConnectorStatus::Connected { + break 'fetch_edid; + } + let edid = match props.get("EDID") { + Ok(e) => e, + _ => { + log::warn!( + "Connector {} is connected but has no EDID blob", + connector_id, + ); + break 'fetch_edid; + } + }; + let blob = match dev.master.getblob_vec::(DrmBlob(edid.value as _)) { + Ok(b) => b, + Err(e) => { + log::error!( + "Could not fetch edid property of connector {}: {}", + connector_id, + ErrorFmt(e) + ); + break 'fetch_edid; + } + }; + let edid = match crate::edid::parse(&blob) { + Ok(e) => e, + Err(e) => { + log::error!( + "Could not parse edid property of connector {}: {}", + connector_id, + ErrorFmt(e) + ); + break 'fetch_edid; + } + }; + manufacturer = edid.base_block.id_manufacturer_name.to_string(); + for descriptor in edid.base_block.descriptors.iter().flatten() { + match descriptor { + Descriptor::DisplayProductSerialNumber(s) => { + serial_number.clone_from(s); + } + Descriptor::DisplayProductName(s) => { + name.clone_from(s); + } + _ => {} + } + } + if name.is_empty() { + log::warn!( + "The display attached to connector {} does not have a product name descriptor", + connector_id, + ); + } + if serial_number.is_empty() { + log::warn!( + "The display attached to connector {} does not have a serial number descriptor", + connector_id, + ); + serial_number = edid.base_block.id_serial_number.to_string(); + } + let min_vrr_hz = 'fetch_min_hz: { + for ext in &edid.extension_blocks { + if let EdidExtension::CtaV3(cta) = ext { + for data_block in &cta.data_blocks { + if let CtaDataBlock::VendorAmd(amd) = data_block { + break 'fetch_min_hz amd.minimum_refresh_hz as u64; + } + } + } + } + for desc in &edid.base_block.descriptors { + if let Some(desc) = desc + && let Descriptor::DisplayRangeLimitsAndAdditionalTiming(timings) = desc + { + break 'fetch_min_hz timings.vertical_field_rate_min as u64; + } + } + 0 + }; + if min_vrr_hz > 0 { + vrr_refresh_max_nsec = 1_000_000_000 / min_vrr_hz; + } + let cc = &edid.base_block.chromaticity_coordinates; + let map = |c: u16| F64(c as f64 / 1024.0); + primaries = Primaries { + r: (map(cc.red_x), map(cc.red_y)), + g: (map(cc.green_x), map(cc.green_y)), + b: (map(cc.blue_x), map(cc.blue_y)), + wp: (map(cc.white_x), map(cc.white_y)), + }; + for ext in &edid.extension_blocks { + if let EdidExtension::CtaV3(cta) = ext { + for data_block in &cta.data_blocks { + match data_block { + CtaDataBlock::Colorimetry(c) => { + if c.bt2020_rgb { + supports_bt2020 = true; + } + } + CtaDataBlock::StaticHdrMetadata(h) => { + if h.smpte_st_2084 { + supports_pq = true; + } + if let Some(max) = h.max_luminance { + luminance = Some(BackendLuminance { + min: h.min_luminance.unwrap_or(0.0), + max, + max_fall: h.max_luminance.unwrap_or(max), + }); + } + } + _ => {} + } + } + } + } + } + let output_id = OutputId::new(connector_id.to_string(), manufacturer, name, serial_number); + let first_mode = info + .modes + .first() + .cloned() + .map(|m| m.to_backend()) + .unwrap_or_default(); + let persistent = match dev.backend.persistent_display_data.get(&output_id) { + Some(ds) => { + if connection != ConnectorStatus::Disconnected { + log::info!("Reusing desired state for {:?}", output_id); + } + ds + } + None => { + let ds = Rc::new(PersistentDisplayData { + state: RefCell::new(BackendConnectorState { + serial: dev.backend.state.backend_connector_state_serials.next(), + enabled: true, + active: true, + mode: first_mode, + non_desktop_override: None, + vrr: false, + tearing: false, + format: XRGB8888, + color_space: Default::default(), + eotf: Default::default(), + gamma_lut: Default::default(), + }), + }); + dev.backend + .persistent_display_data + .set(output_id.clone(), ds.clone()); + ds + } + }; + let mut desired_state = persistent.state.borrow_mut(); + if desired_state.mode == Mode::default() { + desired_state.mode = first_mode; + } else if info + .modes + .iter() + .all(|m| m.to_backend() != desired_state.mode) + { + log::warn!("Discarding previously desired mode"); + desired_state.mode = first_mode; + } + let non_desktop = props.get("non-desktop")?.value != 0; + let vrr_capable = match props.get("vrr_capable") { + Ok(c) => c.value == 1, + Err(_) => false, + }; + if !vrr_capable && desired_state.vrr { + log::warn!("Connector has lost VRR capability"); + desired_state.vrr = false; + } + { + let viable = match desired_state.eotf { + BackendEotfs::Default => true, + BackendEotfs::Pq => supports_pq, + }; + if !viable { + log::warn!("Discarding previously desired EOTF"); + desired_state.eotf = BackendEotfs::Default; + } + } + { + let viable = match desired_state.color_space { + BackendColorSpace::Default => true, + BackendColorSpace::Bt2020 => supports_bt2020, + }; + if !viable { + log::warn!("Discarding previously desired color space"); + desired_state.color_space = BackendColorSpace::Default; + } + } + drop(desired_state); + let default_properties = create_default_properties( + &props, + &[ + ("Broadcast RGB", DefaultValue::Enum("Automatic")), + ("HDR_SOURCE_METADATA", DefaultValue::Fixed(0)), + ("Output format", DefaultValue::Enum("Default")), + ("WRITEBACK_FB_ID", DefaultValue::Fixed(0)), + ("WRITEBACK_OUT_FENCE_PTR", DefaultValue::Fixed(0)), + ("content type", DefaultValue::Enum("No Data")), + ("dither", DefaultValue::Enum("off")), + ("max bpc", DefaultValue::RangeMax), + ], + ); + let hdr_metadata_prop = props + .get("HDR_OUTPUT_METADATA") + .map(|p| p.map(|v| DrmBlob(v as _))) + .ok(); + let mut hdr_metadata = None; + let mut hdr_metadata_blob_id = DrmBlob::NONE; + if let Some(p) = &hdr_metadata_prop { + hdr_metadata_blob_id = p.value; + hdr_metadata = Some(hdr_output_metadata::from_eotf( + HDMI_EOTF_TRADITIONAL_GAMMA_SDR, + )); + if p.value.is_some() { + match dev.master.getblob::(p.value) { + Ok(m) => hdr_metadata = Some(m), + _ => { + log::debug!("Could not retrieve hdr output metadata"); + } + } + } + } + let colorspace_prop = props.get("Colorspace").ok(); + let crtc_id = props.get("CRTC_ID")?.map(|v| DrmCrtc(v as _)); + let drm_state = DrmConnectorState { + crtc_id: crtc_id.value, + color_space: colorspace_prop.map(|p| p.value), + hdr_metadata, + hdr_metadata_blob_id, + hdr_metadata_blob: None, + locked: true, + fb: DrmFb::NONE, + fb_idx: 0, + cursor_fb: DrmFb::NONE, + cursor_fb_idx: 0, + cursor_x: 0, + cursor_y: 0, + out_fd: None, + src_w: 0, + src_h: 0, + crtc_x: 0, + crtc_y: 0, + crtc_w: 0, + crtc_h: 0, + }; + Ok(ConnectorDisplayData { + crtc_id: props.get("CRTC_ID")?.id, + crtcs, + first_mode, + modes: info.modes, + persistent, + refresh: 0, + non_desktop, + non_desktop_effective: non_desktop, + vrr_capable, + _vrr_refresh_max_nsec: vrr_refresh_max_nsec, + default_properties, + untyped_properties: props.to_untyped(), + connection, + mm_width: info.mm_width, + mm_height: info.mm_height, + _subpixel: info.subpixel, + supports_bt2020, + supports_pq, + primaries, + luminance, + connector_id, + output_id, + colorspace: colorspace_prop.map(|p| p.id), + hdr_metadata: hdr_metadata_prop.map(|p| p.id), + drm_state, + }) +} + +pub(super) fn create_encoder( + encoder: DrmEncoder, + master: &Rc, + crtcs: &AHashMap>, +) -> Result { + let info = master.get_encoder_info(encoder)?; + let mut possible = AHashMap::new(); + for crtc in crtcs.values() { + if info.possible_crtcs.contains(1 << crtc.idx) { + possible.insert(crtc.id, crtc.clone()); + } + } + Ok(MetalEncoder { + id: encoder, + crtcs: possible, + }) +} + +pub(super) fn create_crtc( + crtc: DrmCrtc, + idx: usize, + master: &Rc, + planes: &AHashMap>, +) -> Result { + let mask = 1 << idx; + let mut possible_planes = BinarySearchMap::new(); + for plane in planes.values() { + if plane.possible_crtcs.contains(mask) { + possible_planes.insert(plane.id, plane.clone()); + } + } + let props = collect_properties(master, crtc)?; + let default_properties = create_default_properties( + &props, + &[ + ("AMD_CRTC_REGAMMA_TF", DefaultValue::Enum("Default")), + ("CTM", DefaultValue::Fixed(0)), + ("DEGAMMA_LUT", DefaultValue::Fixed(0)), + ("OUT_FENCE_PTR", DefaultValue::Fixed(0)), + ], + ); + let active = props.get("ACTIVE")?.map(|v| v == 1); + let mode_id = props.get("MODE_ID")?.map(|v| DrmBlob(v as u32)); + let vrr_enabled = props.get("VRR_ENABLED")?.map(|v| v == 1); + let out_fence_ptr = props.get("OUT_FENCE_PTR")?; + let gamma_lut = props + .get("GAMMA_LUT") + .ok() + .map(|v| v.map(|v| DrmBlob(v as u32))); + let mut gamma_lut_size = None; + if gamma_lut.is_some() { + gamma_lut_size = props.get("GAMMA_LUT_SIZE").ok().map(|v| v.value as u32); + } + let mut mode = None; + if mode_id.value.is_some() { + match master.getblob::(mode_id.value) { + Ok(m) => mode = Some(m.into()), + _ => { + log::debug!("Could not retrieve current mode of connector"); + } + } + } + let state = DrmCrtcState { + active: active.value, + mode, + mode_blob_id: mode_id.value, + mode_blob: None, + vrr_enabled: vrr_enabled.value, + assigned_connector: DrmConnector::NONE, + gamma_lut: None, + gamma_lut_blob_id: gamma_lut.map_or(DrmBlob::NONE, |v| v.value), + gamma_lut_blob: None, + }; + Ok(MetalCrtc { + id: crtc, + idx, + master: master.clone(), + default_properties, + untyped_properties: RefCell::new(props.to_untyped()), + lease: Cell::new(None), + possible_planes, + connector: Default::default(), + pending_flip: Default::default(), + drm_state: RefCell::new(state), + active: active.id, + mode_id: mode_id.id, + vrr_enabled: vrr_enabled.id, + out_fence_ptr: out_fence_ptr.id, + gamma_lut: gamma_lut.map(|v| v.id), + gamma_lut_size, + sequence: Cell::new(0), + have_queued_sequence: Cell::new(false), + needs_vblank_emulation: Cell::new(false), + }) +} + +pub(super) fn create_plane(plane: DrmPlane, master: &Rc) -> Result { + let info = master.get_plane_info(plane)?; + let props = collect_properties(master, plane)?; + let mut formats = AHashMap::new(); + if let Some((_, v)) = props.props.get(b"IN_FORMATS".as_bstr()) { + for format in master.get_in_formats(*v as _)? { + if format.modifiers.is_empty() { + continue; + } + if let Some(f) = crate::format::formats().get(&format.format) { + formats.insert( + format.format, + PlaneFormat { + format: f, + modifiers: format.modifiers, + }, + ); + } + } + } else { + for format in info.format_types { + if let Some(f) = crate::format::formats().get(&format) { + formats.insert( + format, + PlaneFormat { + format: f, + modifiers: indexset![INVALID_MODIFIER], + }, + ); + } + } + } + let ty = match props.props.get(b"type".as_bstr()) { + Some((def, val)) => match &def.ty { + DrmPropertyType::Enum { values, .. } => 'ty: { + for v in values { + if v.value == *val { + match v.name.as_bytes() { + b"Overlay" => break 'ty PlaneType::Overlay, + b"Primary" => break 'ty PlaneType::Primary, + b"Cursor" => break 'ty PlaneType::Cursor, + _ => return Err(DrmError::UnknownPlaneType(v.name.to_owned())), + } + } + } + return Err(DrmError::InvalidPlaneType(*val)); + } + _ => return Err(DrmError::InvalidPlaneTypeProperty), + }, + _ => { + return Err(DrmError::MissingProperty( + "type".to_string().into_boxed_str(), + )); + } + }; + let default_properties = create_default_properties( + &props, + &[ + ("AMD_PLANE_BLEND_LUT", DefaultValue::Fixed(0)), + ("AMD_PLANE_BLEND_TF", DefaultValue::Enum("Default")), + ("AMD_PLANE_CTM", DefaultValue::Fixed(0)), + ("AMD_PLANE_DEGAMMA_LUT", DefaultValue::Fixed(0)), + ("AMD_PLANE_HDR_MULT", DefaultValue::Fixed(0)), + ("AMD_PLANE_LUT3D", DefaultValue::Fixed(0)), + ("AMD_PLANE_SHAPER_LUT", DefaultValue::Fixed(0)), + ("AMD_PLANE_SHAPER_TF", DefaultValue::Enum("Default")), + ("alpha", DefaultValue::RangeMax), + ("pixel blend mode", DefaultValue::Enum("Pre-multiplied")), + ("rotation", DefaultValue::Bitmask(&["rotate-0"])), + ], + ); + let fb_id = props.get("FB_ID")?.map(|v| DrmFb(v as _)); + let crtc_id = props.get("CRTC_ID")?.map(|v| DrmCrtc(v as _)); + let crtc_x = props.get("CRTC_X")?.map(|v| v as i32); + let crtc_y = props.get("CRTC_Y")?.map(|v| v as i32); + let crtc_w = props.get("CRTC_W")?.map(|v| v as i32); + let crtc_h = props.get("CRTC_H")?.map(|v| v as i32); + let src_x = props.get("SRC_X")?.map(|v| v as u32); + let src_y = props.get("SRC_Y")?.map(|v| v as u32); + let src_w = props.get("SRC_W")?.map(|v| v as u32); + let src_h = props.get("SRC_H")?.map(|v| v as u32); + let in_fence_fd = props.get("IN_FENCE_FD")?; + let state = DrmPlaneState { + fb_id: fb_id.value, + src_x: src_x.value, + src_y: src_y.value, + src_w: src_w.value, + src_h: src_h.value, + assigned_crtc: DrmCrtc::NONE, + crtc_id: crtc_id.value, + crtc_x: crtc_x.value, + crtc_y: crtc_y.value, + crtc_w: crtc_w.value, + crtc_h: crtc_h.value, + buffers: None, + }; + Ok(MetalPlane { + id: plane, + master: master.clone(), + default_properties, + untyped_properties: RefCell::new(props.to_untyped()), + ty, + possible_crtcs: info.possible_crtcs, + formats, + drm_state: RefCell::new(state), + fb_id: fb_id.id, + crtc_id: crtc_id.id, + crtc_x: crtc_x.id, + crtc_y: crtc_y.id, + crtc_w: crtc_w.id, + crtc_h: crtc_h.id, + src_x: src_x.id, + src_y: src_y.id, + src_w: src_w.id, + src_h: src_h.id, + in_fence_fd: in_fence_fd.id, + mode_w: Cell::new(0), + mode_h: Cell::new(0), + lease: Cell::new(None), + }) +} diff --git a/src/copy_device.rs b/src/copy_device.rs index 70191dd0..3f34a533 100644 --- a/src/copy_device.rs +++ b/src/copy_device.rs @@ -5,7 +5,6 @@ use { format::{FORMATS, Format}, gfx_api::FdSync, io_uring::IoUring, - rect::{Rect, Region}, utils::{ clonecell::CloneCell, errorfmt::ErrorFmt, @@ -21,12 +20,10 @@ use { }, vulkan_core::{ self, VULKAN_API_VERSION, VulkanCoreError, VulkanCoreInstance, device::VulkanDeviceInf, - map_extension_properties, sync::VulkanDeviceSyncExt, - timeline_semaphore::VulkanDeviceTimelineSemaphoreExt, + map_extension_properties, timeline_semaphore::VulkanDeviceTimelineSemaphoreExt, }, }, ahash::AHashMap, - arrayvec::ArrayVec, ash::{ Device, ext::{ @@ -35,12 +32,9 @@ use { }, khr::{external_fence_fd, external_memory_fd, external_semaphore_fd}, vk::{ - self, AccessFlags2, BindImageMemoryInfo, BindImagePlaneMemoryInfo, BlitImageInfo2, - BufferCopy2, BufferCreateInfo, BufferImageCopy2, BufferMemoryBarrier2, - BufferUsageFlags, CommandBuffer, CommandBufferAllocateInfo, CommandBufferBeginInfo, - CommandBufferSubmitInfo, CommandBufferUsageFlags, CommandPoolCreateFlags, - CommandPoolCreateInfo, CopyBufferInfo2, CopyBufferToImageInfo2, CopyImageInfo2, - CopyImageToBufferInfo2, DependencyInfo, DeviceCreateInfo, DeviceMemory, + self, BindImageMemoryInfo, BindImagePlaneMemoryInfo, BufferCopy2, BufferCreateInfo, + BufferImageCopy2, BufferUsageFlags, CommandBuffer, CommandBufferAllocateInfo, + CommandPoolCreateFlags, CommandPoolCreateInfo, DeviceCreateInfo, DeviceMemory, DeviceQueueCreateInfo, DrmFormatModifierPropertiesEXT, DrmFormatModifierPropertiesListEXT, ExportMemoryAllocateInfo, Extent3D, ExternalBufferProperties, ExternalFenceFeatureFlags, ExternalFenceHandleTypeFlags, @@ -48,23 +42,21 @@ use { ExternalMemoryBufferCreateInfo, ExternalMemoryBufferCreateInfoKHR, ExternalMemoryFeatureFlags, ExternalMemoryHandleTypeFlags, ExternalMemoryImageCreateInfo, ExternalSemaphoreFeatureFlags, - ExternalSemaphoreHandleTypeFlags, ExternalSemaphoreProperties, Filter, - FormatFeatureFlags, FormatProperties2, ImageAspectFlags, ImageBlit2, ImageCopy2, - ImageCreateFlags, ImageCreateInfo, ImageDrmFormatModifierExplicitCreateInfoEXT, - ImageFormatProperties2, ImageLayout, ImageMemoryBarrier2, ImageMemoryRequirementsInfo2, - ImagePlaneMemoryRequirementsInfo, ImageSubresourceLayers, ImageSubresourceRange, - ImageTiling, ImageType, ImageUsageFlags, ImportMemoryFdInfoKHR, - ImportSemaphoreFdInfoKHR, MemoryAllocateInfo, MemoryDedicatedAllocateInfo, - MemoryFdPropertiesKHR, MemoryGetFdInfoKHR, MemoryPropertyFlags, MemoryRequirements2, - MemoryType, Offset3D, PhysicalDevice, PhysicalDeviceDrmPropertiesEXT, + ExternalSemaphoreHandleTypeFlags, ExternalSemaphoreProperties, FormatFeatureFlags, + FormatProperties2, ImageAspectFlags, ImageBlit2, ImageCopy2, ImageCreateFlags, + ImageCreateInfo, ImageDrmFormatModifierExplicitCreateInfoEXT, ImageFormatProperties2, + ImageLayout, ImageMemoryRequirementsInfo2, ImagePlaneMemoryRequirementsInfo, + ImageTiling, ImageType, ImageUsageFlags, ImportMemoryFdInfoKHR, ImportSemaphoreFdInfoKHR, + MemoryAllocateInfo, MemoryDedicatedAllocateInfo, MemoryFdPropertiesKHR, + MemoryGetFdInfoKHR, MemoryPropertyFlags, MemoryRequirements2, MemoryType, + PhysicalDevice, PhysicalDeviceDrmPropertiesEXT, PhysicalDeviceExternalBufferInfo, PhysicalDeviceExternalFenceInfo, PhysicalDeviceExternalImageFormatInfoKHR, PhysicalDeviceExternalSemaphoreInfo, PhysicalDeviceFeatures2, PhysicalDeviceImageDrmFormatModifierInfoEXT, PhysicalDeviceImageFormatInfo2, PhysicalDeviceProperties2, PhysicalDeviceSynchronization2Features, PhysicalDeviceTimelineSemaphoreFeatures, - PipelineStageFlags2, QUEUE_FAMILY_FOREIGN_EXT, Queue, QueueFlags, SampleCountFlags, - SemaphoreCreateInfo, SemaphoreImportFlags, SemaphoreSubmitInfo, SharingMode, - SubmitInfo2, SubresourceLayout, WHOLE_SIZE, + Queue, QueueFlags, SampleCountFlags, SemaphoreCreateInfo, SemaphoreImportFlags, + SharingMode, SubresourceLayout, }, }, bstr::ByteSlice, @@ -85,6 +77,7 @@ use { vk::{Buffer, CommandPool, Image, Semaphore}, }; +mod execute; mod queue_allocation; mod registry; @@ -1275,429 +1268,6 @@ impl CopyDeviceInner { } } -impl CopyDeviceCopy { - fn ensure_not_busy(&self) -> Result<(), CopyDeviceError> { - let slf = &*self.inner; - if let Some(sync) = slf.busy.get() - && sync.is_unsignaled() - { - return Err(CopyDeviceError::Busy); - } - slf.busy.take(); - Ok(()) - } - - pub fn execute( - &self, - sync: Option<&FdSync>, - region: Option<&Region>, - ) -> Result, CopyDeviceError> { - self.ensure_not_busy()?; - let slf = &*self.inner; - let tt = slf.tt; - let dev = &slf.dev.dev; - let cmd = slf.command_buffer; - let queue_family = slf.dev.phy.queues[tt].family; - let region_buf; - let width = slf.width; - let height = slf.height; - let region = match region { - Some(r) => r, - _ => { - region_buf = Region::new(Rect::new_saturating(0, 0, width as i32, height as i32)); - ®ion_buf - } - }; - let (x_mask, y_mask) = slf.dev.phy.queues[tt].transfer_granularity_mask; - let rects = &mut *slf.dev.phy.rects.borrow_mut(); - rects.clear(); - for rect in region.iter() { - let x1 = (rect.x1().max(0) as u32 & !x_mask).min(width); - let y1 = (rect.y1().max(0) as u32 & !y_mask).min(height); - let x2 = ((rect.x2().max(0) as u32 + x_mask) & !x_mask).min(width); - let y2 = ((rect.y2().max(0) as u32 + y_mask) & !y_mask).min(height); - let width = x2 - x1; - let height = y2 - y1; - if width == 0 || height == 0 { - continue; - } - rects.push((x1 as i32, y1 as i32, width, height)); - } - if rects.is_empty() { - return Ok(None); - } - let begin_info = - CommandBufferBeginInfo::default().flags(CommandBufferUsageFlags::ONE_TIME_SUBMIT); - unsafe { - dev.begin_command_buffer(cmd, &begin_info) - .map_err(CopyDeviceError::BeginCommandBuffer)?; - } - macro_rules! initial_buffer_barriers { - ($($buf:expr, $access:expr;)*) => { - [$( - BufferMemoryBarrier2::default() - .dst_stage_mask(PipelineStageFlags2::TRANSFER) - .dst_access_mask($access) - .src_queue_family_index(QUEUE_FAMILY_FOREIGN_EXT) - .dst_queue_family_index(queue_family) - .buffer($buf.buf) - .size(WHOLE_SIZE), - )*] - }; - } - macro_rules! final_buffer_barriers { - ($($buf:expr, $access:expr;)*) => { - [$( - BufferMemoryBarrier2::default() - .src_stage_mask(PipelineStageFlags2::TRANSFER) - .src_access_mask($access) - .src_queue_family_index(queue_family) - .dst_queue_family_index(QUEUE_FAMILY_FOREIGN_EXT) - .buffer($buf.buf) - .size(WHOLE_SIZE), - )*] - }; - } - let image_subresource_range = ImageSubresourceRange { - aspect_mask: ImageAspectFlags::COLOR, - base_mip_level: 0, - level_count: 1, - base_array_layer: 0, - layer_count: 1, - }; - let image_subresource = ImageSubresourceLayers { - aspect_mask: ImageAspectFlags::COLOR, - mip_level: 0, - base_array_layer: 0, - layer_count: 1, - }; - macro_rules! initial_image_barriers { - ($($img:expr, $layout:expr, $access:expr;)*) => { - [$( - ImageMemoryBarrier2::default() - .dst_stage_mask(PipelineStageFlags2::TRANSFER) - .dst_access_mask($access) - .old_layout(ImageLayout::GENERAL) - .new_layout(ImageLayout::GENERAL) - .src_queue_family_index(QUEUE_FAMILY_FOREIGN_EXT) - .dst_queue_family_index(queue_family) - .image($img.img) - .subresource_range(image_subresource_range), - ImageMemoryBarrier2::default() - .src_stage_mask(PipelineStageFlags2::TRANSFER) - .src_access_mask($access) - .dst_stage_mask(PipelineStageFlags2::TRANSFER) - .dst_access_mask($access) - .old_layout(ImageLayout::GENERAL) - .new_layout($layout) - .src_queue_family_index(queue_family) - .dst_queue_family_index(queue_family) - .image($img.img) - .subresource_range(image_subresource_range), - )*] - }; - } - macro_rules! final_image_barriers { - ($($img:expr, $layout:expr, $access:expr;)*) => { - [$( - ImageMemoryBarrier2::default() - .src_stage_mask(PipelineStageFlags2::TRANSFER) - .src_access_mask($access) - .dst_stage_mask(PipelineStageFlags2::TRANSFER) - .dst_access_mask($access) - .old_layout($layout) - .new_layout(ImageLayout::GENERAL) - .src_queue_family_index(queue_family) - .dst_queue_family_index(queue_family) - .image($img.img) - .subresource_range(image_subresource_range), - ImageMemoryBarrier2::default() - .src_stage_mask(PipelineStageFlags2::TRANSFER) - .src_access_mask($access) - .old_layout(ImageLayout::GENERAL) - .new_layout(ImageLayout::GENERAL) - .src_queue_family_index(queue_family) - .dst_queue_family_index(QUEUE_FAMILY_FOREIGN_EXT) - .image($img.img) - .subresource_range(image_subresource_range), - )*] - }; - } - match &slf.ty { - CopyDeviceCopyType::BufferToBuffer { - src, - dst, - stride, - bpp, - } => { - let regions = &mut *slf.dev.phy.buffer_copy_2.borrow_mut(); - regions.clear(); - let stride = *stride as u64; - let bpp = *bpp as u64; - for &mut (x, y, width, height) in rects { - let lo = y as u64 * stride + x as u64 * bpp; - let size = (height as u64 - 1) * stride + width as u64 * bpp; - let region = BufferCopy2::default() - .src_offset(lo) - .dst_offset(lo) - .size(size); - regions.push(region); - } - use AccessFlags2 as A; - let initial_barriers = initial_buffer_barriers![ - src, A::TRANSFER_READ; - dst, A::TRANSFER_WRITE; - ]; - let final_barriers = final_buffer_barriers![ - src, A::TRANSFER_READ; - dst, A::TRANSFER_WRITE; - ]; - let initial_dependency_info = - DependencyInfo::default().buffer_memory_barriers(&initial_barriers); - let final_dependency_info = - DependencyInfo::default().buffer_memory_barriers(&final_barriers); - let copy_buffer_info = CopyBufferInfo2::default() - .src_buffer(src.buf) - .dst_buffer(dst.buf) - .regions(regions); - unsafe { - dev.cmd_pipeline_barrier2(cmd, &initial_dependency_info); - dev.cmd_copy_buffer2(cmd, ©_buffer_info); - dev.cmd_pipeline_barrier2(cmd, &final_dependency_info); - } - } - CopyDeviceCopyType::BufferToImage { - buf, - buf_format, - buf_stride, - img, - } - | CopyDeviceCopyType::ImageToBuffer { - img, - buf, - buf_format, - buf_stride, - } => { - let regions = &mut *slf.dev.phy.buffer_image_copy_2.borrow_mut(); - regions.clear(); - for &mut (x, y, width, height) in rects { - let offset = y as u64 * *buf_stride as u64 + x as u64 * buf_format.bpp as u64; - let region = BufferImageCopy2::default() - .buffer_offset(offset) - .buffer_row_length(*buf_stride / buf_format.bpp) - .buffer_image_height(slf.height) - .image_subresource(image_subresource) - .image_offset(Offset3D { x, y, z: 0 }) - .image_extent(Extent3D { - width, - height, - depth: 1, - }); - regions.push(region); - } - let buffer_to_image = match &slf.ty { - CopyDeviceCopyType::BufferToImage { .. } => true, - CopyDeviceCopyType::ImageToBuffer { .. } => false, - _ => unreachable!(), - }; - let image_access_mask; - let image_layout; - let buffer_access_mask; - match buffer_to_image { - true => { - image_access_mask = AccessFlags2::TRANSFER_WRITE; - image_layout = ImageLayout::TRANSFER_DST_OPTIMAL; - buffer_access_mask = AccessFlags2::TRANSFER_READ; - } - false => { - image_access_mask = AccessFlags2::TRANSFER_READ; - image_layout = ImageLayout::TRANSFER_SRC_OPTIMAL; - buffer_access_mask = AccessFlags2::TRANSFER_WRITE; - } - } - let initial_image_barriers = initial_image_barriers![ - img, image_layout, image_access_mask; - ]; - let final_image_barriers = final_image_barriers![ - img, image_layout, image_access_mask; - ]; - let initial_buffer_barriers = initial_buffer_barriers![ - buf, buffer_access_mask; - ]; - let final_buffer_barriers = final_buffer_barriers![ - buf, buffer_access_mask; - ]; - let initial_dependency_info = DependencyInfo::default() - .buffer_memory_barriers(&initial_buffer_barriers) - .image_memory_barriers(&initial_image_barriers); - let final_dependency_info = DependencyInfo::default() - .buffer_memory_barriers(&final_buffer_barriers) - .image_memory_barriers(&final_image_barriers); - unsafe { - dev.cmd_pipeline_barrier2(cmd, &initial_dependency_info); - match buffer_to_image { - true => { - let copy = CopyBufferToImageInfo2::default() - .src_buffer(buf.buf) - .dst_image(img.img) - .dst_image_layout(image_layout) - .regions(®ions); - dev.cmd_copy_buffer_to_image2(cmd, ©); - } - false => { - let copy = CopyImageToBufferInfo2::default() - .src_image(img.img) - .src_image_layout(image_layout) - .dst_buffer(buf.buf) - .regions(®ions); - dev.cmd_copy_image_to_buffer2(cmd, ©); - } - } - dev.cmd_pipeline_barrier2(cmd, &final_dependency_info); - } - } - CopyDeviceCopyType::ImageToImage { src, dst } => { - let regions = &mut *slf.dev.phy.image_copy_2.borrow_mut(); - regions.clear(); - for &mut (x, y, width, height) in rects { - let region = ImageCopy2::default() - .src_subresource(image_subresource) - .src_offset(Offset3D { x, y, z: 0 }) - .dst_subresource(image_subresource) - .dst_offset(Offset3D { x, y, z: 0 }) - .extent(Extent3D { - width, - height, - depth: 1, - }); - regions.push(region); - } - use {AccessFlags2 as A, ImageLayout as L}; - let initial_barriers = initial_image_barriers![ - src, L::TRANSFER_SRC_OPTIMAL, A::TRANSFER_READ; - dst, L::TRANSFER_DST_OPTIMAL, A::TRANSFER_WRITE; - ]; - let final_barriers = final_image_barriers![ - src, L::TRANSFER_SRC_OPTIMAL, A::TRANSFER_READ; - dst, L::TRANSFER_DST_OPTIMAL, A::TRANSFER_WRITE; - ]; - let initial_dependency_info = - DependencyInfo::default().image_memory_barriers(&initial_barriers); - let final_dependency_info = - DependencyInfo::default().image_memory_barriers(&final_barriers); - let copy_image_info = CopyImageInfo2::default() - .src_image(src.img) - .src_image_layout(L::TRANSFER_SRC_OPTIMAL) - .dst_image(dst.img) - .dst_image_layout(L::TRANSFER_DST_OPTIMAL) - .regions(regions); - unsafe { - dev.cmd_pipeline_barrier2(cmd, &initial_dependency_info); - dev.cmd_copy_image2(cmd, ©_image_info); - dev.cmd_pipeline_barrier2(cmd, &final_dependency_info); - } - } - CopyDeviceCopyType::Blit { src, dst } => { - let regions = &mut *slf.dev.phy.image_blit_2.borrow_mut(); - regions.clear(); - for &mut (x, y, width, height) in rects { - let x1 = x; - let y1 = y; - let x2 = x1 + width as i32; - let y2 = y1 + height as i32; - let offsets = [ - Offset3D { x: x1, y: y1, z: 0 }, - Offset3D { x: x2, y: y2, z: 1 }, - ]; - let region = ImageBlit2::default() - .src_subresource(image_subresource) - .src_offsets(offsets) - .dst_subresource(image_subresource) - .dst_offsets(offsets); - regions.push(region); - } - use {AccessFlags2 as A, ImageLayout as L}; - let initial_barriers = initial_image_barriers![ - src, L::TRANSFER_SRC_OPTIMAL, A::TRANSFER_READ; - dst, L::TRANSFER_DST_OPTIMAL, A::TRANSFER_WRITE; - ]; - let final_barriers = final_image_barriers![ - src, L::TRANSFER_SRC_OPTIMAL, A::TRANSFER_READ; - dst, L::TRANSFER_DST_OPTIMAL, A::TRANSFER_WRITE; - ]; - let initial_dependency_info = - DependencyInfo::default().image_memory_barriers(&initial_barriers); - let final_dependency_info = - DependencyInfo::default().image_memory_barriers(&final_barriers); - let blit_image_info = BlitImageInfo2::default() - .src_image(src.img) - .src_image_layout(L::TRANSFER_SRC_OPTIMAL) - .dst_image(dst.img) - .dst_image_layout(L::TRANSFER_DST_OPTIMAL) - .regions(regions) - .filter(Filter::NEAREST); - unsafe { - dev.cmd_pipeline_barrier2(cmd, &initial_dependency_info); - dev.cmd_blit_image2(cmd, &blit_image_info); - dev.cmd_pipeline_barrier2(cmd, &final_dependency_info); - } - } - }; - unsafe { - dev.end_command_buffer(cmd) - .map_err(CopyDeviceError::EndCommandBuffer)?; - } - let mut wait_semaphore = None; - let mut wait_semaphores = ArrayVec::<_, 1>::new(); - if let Some(sync) = sync - && let Some(sync_file) = sync.get_sync_file() - { - let semaphore = match slf.dev.semaphores.pop() { - Some(s) => s, - _ => slf.dev.create_semaphore()?, - }; - semaphore.import(sync_file)?; - let info = SemaphoreSubmitInfo::default() - .semaphore(semaphore.semaphore) - .stage_mask(PipelineStageFlags2::TRANSFER); - wait_semaphores.push(info); - wait_semaphore = Some(semaphore); - } - let command_buffer_info = CommandBufferSubmitInfo::default().command_buffer(cmd); - let mut semaphore_submit_info = SemaphoreSubmitInfo::default(); - let mut submit_info = SubmitInfo2::default() - .command_buffer_infos(slice::from_ref(&command_buffer_info)) - .wait_semaphore_infos(&wait_semaphores); - let vulkan_sync = slf.dev.create_sync( - self.dev.timeline_semaphore.as_ref(), - &mut semaphore_submit_info, - &mut submit_info, - )?; - unsafe { - slf.dev - .dev - .queue_submit2( - slf.dev.queues[tt], - slice::from_ref(&submit_info), - vulkan_sync.fence(), - ) - .map_err(CopyDeviceError::SubmitCopy)?; - } - let sync = vulkan_sync.to_sync(|| slf.dev.wait_idle()); - slf.busy.set(sync.clone()); - let pending = Pending { - dev: slf.dev.clone(), - busy_id: slf.busy_id.add_fetch(1), - sync: sync.clone(), - copy: self.inner.clone(), - semaphore: wait_semaphore, - vulkan_sync, - }; - slf.dev.submissions[tt].pending.push(pending); - Ok(sync) - } -} - impl VulkanSemaphore { fn import(&self, sync_file: &OwnedFd) -> Result<(), CopyDeviceError> { let fd = uapi::fcntl_dupfd_cloexec(sync_file.raw(), 0) diff --git a/src/copy_device/execute.rs b/src/copy_device/execute.rs new file mode 100644 index 00000000..7f1544e7 --- /dev/null +++ b/src/copy_device/execute.rs @@ -0,0 +1,442 @@ +use { + super::{CopyDeviceCopy, CopyDeviceCopyType, CopyDeviceError, Pending}, + crate::{ + gfx_api::FdSync, + rect::{Rect, Region}, + vulkan_core::sync::VulkanDeviceSyncExt, + }, + arrayvec::ArrayVec, + ash::vk::{ + AccessFlags2, BlitImageInfo2, BufferCopy2, BufferImageCopy2, BufferMemoryBarrier2, + CommandBufferBeginInfo, CommandBufferSubmitInfo, CommandBufferUsageFlags, + CopyBufferInfo2, CopyBufferToImageInfo2, CopyImageInfo2, CopyImageToBufferInfo2, + DependencyInfo, Extent3D, Filter, ImageAspectFlags, ImageBlit2, ImageCopy2, ImageLayout, + ImageMemoryBarrier2, ImageSubresourceLayers, ImageSubresourceRange, Offset3D, + PipelineStageFlags2, QUEUE_FAMILY_FOREIGN_EXT, SemaphoreSubmitInfo, SubmitInfo2, + WHOLE_SIZE, + }, + std::slice, +}; + +impl CopyDeviceCopy { + fn ensure_not_busy(&self) -> Result<(), CopyDeviceError> { + let slf = &*self.inner; + if let Some(sync) = slf.busy.get() + && sync.is_unsignaled() + { + return Err(CopyDeviceError::Busy); + } + slf.busy.take(); + Ok(()) + } + + pub fn execute( + &self, + sync: Option<&FdSync>, + region: Option<&Region>, + ) -> Result, CopyDeviceError> { + self.ensure_not_busy()?; + let slf = &*self.inner; + let tt = slf.tt; + let dev = &slf.dev.dev; + let cmd = slf.command_buffer; + let queue_family = slf.dev.phy.queues[tt].family; + let region_buf; + let width = slf.width; + let height = slf.height; + let region = match region { + Some(r) => r, + _ => { + region_buf = Region::new(Rect::new_saturating(0, 0, width as i32, height as i32)); + ®ion_buf + } + }; + let (x_mask, y_mask) = slf.dev.phy.queues[tt].transfer_granularity_mask; + let rects = &mut *slf.dev.phy.rects.borrow_mut(); + rects.clear(); + for rect in region.iter() { + let x1 = (rect.x1().max(0) as u32 & !x_mask).min(width); + let y1 = (rect.y1().max(0) as u32 & !y_mask).min(height); + let x2 = ((rect.x2().max(0) as u32 + x_mask) & !x_mask).min(width); + let y2 = ((rect.y2().max(0) as u32 + y_mask) & !y_mask).min(height); + let width = x2 - x1; + let height = y2 - y1; + if width == 0 || height == 0 { + continue; + } + rects.push((x1 as i32, y1 as i32, width, height)); + } + if rects.is_empty() { + return Ok(None); + } + let begin_info = + CommandBufferBeginInfo::default().flags(CommandBufferUsageFlags::ONE_TIME_SUBMIT); + unsafe { + dev.begin_command_buffer(cmd, &begin_info) + .map_err(CopyDeviceError::BeginCommandBuffer)?; + } + macro_rules! initial_buffer_barriers { + ($($buf:expr, $access:expr;)*) => { + [$( + BufferMemoryBarrier2::default() + .dst_stage_mask(PipelineStageFlags2::TRANSFER) + .dst_access_mask($access) + .src_queue_family_index(QUEUE_FAMILY_FOREIGN_EXT) + .dst_queue_family_index(queue_family) + .buffer($buf.buf) + .size(WHOLE_SIZE), + )*] + }; + } + macro_rules! final_buffer_barriers { + ($($buf:expr, $access:expr;)*) => { + [$( + BufferMemoryBarrier2::default() + .src_stage_mask(PipelineStageFlags2::TRANSFER) + .src_access_mask($access) + .src_queue_family_index(queue_family) + .dst_queue_family_index(QUEUE_FAMILY_FOREIGN_EXT) + .buffer($buf.buf) + .size(WHOLE_SIZE), + )*] + }; + } + let image_subresource_range = ImageSubresourceRange { + aspect_mask: ImageAspectFlags::COLOR, + base_mip_level: 0, + level_count: 1, + base_array_layer: 0, + layer_count: 1, + }; + let image_subresource = ImageSubresourceLayers { + aspect_mask: ImageAspectFlags::COLOR, + mip_level: 0, + base_array_layer: 0, + layer_count: 1, + }; + macro_rules! initial_image_barriers { + ($($img:expr, $layout:expr, $access:expr;)*) => { + [$( + ImageMemoryBarrier2::default() + .dst_stage_mask(PipelineStageFlags2::TRANSFER) + .dst_access_mask($access) + .old_layout(ImageLayout::GENERAL) + .new_layout(ImageLayout::GENERAL) + .src_queue_family_index(QUEUE_FAMILY_FOREIGN_EXT) + .dst_queue_family_index(queue_family) + .image($img.img) + .subresource_range(image_subresource_range), + ImageMemoryBarrier2::default() + .src_stage_mask(PipelineStageFlags2::TRANSFER) + .src_access_mask($access) + .dst_stage_mask(PipelineStageFlags2::TRANSFER) + .dst_access_mask($access) + .old_layout(ImageLayout::GENERAL) + .new_layout($layout) + .src_queue_family_index(queue_family) + .dst_queue_family_index(queue_family) + .image($img.img) + .subresource_range(image_subresource_range), + )*] + }; + } + macro_rules! final_image_barriers { + ($($img:expr, $layout:expr, $access:expr;)*) => { + [$( + ImageMemoryBarrier2::default() + .src_stage_mask(PipelineStageFlags2::TRANSFER) + .src_access_mask($access) + .dst_stage_mask(PipelineStageFlags2::TRANSFER) + .dst_access_mask($access) + .old_layout($layout) + .new_layout(ImageLayout::GENERAL) + .src_queue_family_index(queue_family) + .dst_queue_family_index(queue_family) + .image($img.img) + .subresource_range(image_subresource_range), + ImageMemoryBarrier2::default() + .src_stage_mask(PipelineStageFlags2::TRANSFER) + .src_access_mask($access) + .old_layout(ImageLayout::GENERAL) + .new_layout(ImageLayout::GENERAL) + .src_queue_family_index(queue_family) + .dst_queue_family_index(QUEUE_FAMILY_FOREIGN_EXT) + .image($img.img) + .subresource_range(image_subresource_range), + )*] + }; + } + match &slf.ty { + CopyDeviceCopyType::BufferToBuffer { + src, + dst, + stride, + bpp, + } => { + let regions = &mut *slf.dev.phy.buffer_copy_2.borrow_mut(); + regions.clear(); + let stride = *stride as u64; + let bpp = *bpp as u64; + for &mut (x, y, width, height) in rects { + let lo = y as u64 * stride + x as u64 * bpp; + let size = (height as u64 - 1) * stride + width as u64 * bpp; + let region = BufferCopy2::default() + .src_offset(lo) + .dst_offset(lo) + .size(size); + regions.push(region); + } + use AccessFlags2 as A; + let initial_barriers = initial_buffer_barriers![ + src, A::TRANSFER_READ; + dst, A::TRANSFER_WRITE; + ]; + let final_barriers = final_buffer_barriers![ + src, A::TRANSFER_READ; + dst, A::TRANSFER_WRITE; + ]; + let initial_dependency_info = + DependencyInfo::default().buffer_memory_barriers(&initial_barriers); + let final_dependency_info = + DependencyInfo::default().buffer_memory_barriers(&final_barriers); + let copy_buffer_info = CopyBufferInfo2::default() + .src_buffer(src.buf) + .dst_buffer(dst.buf) + .regions(regions); + unsafe { + dev.cmd_pipeline_barrier2(cmd, &initial_dependency_info); + dev.cmd_copy_buffer2(cmd, ©_buffer_info); + dev.cmd_pipeline_barrier2(cmd, &final_dependency_info); + } + } + CopyDeviceCopyType::BufferToImage { + buf, + buf_format, + buf_stride, + img, + } + | CopyDeviceCopyType::ImageToBuffer { + img, + buf, + buf_format, + buf_stride, + } => { + let regions = &mut *slf.dev.phy.buffer_image_copy_2.borrow_mut(); + regions.clear(); + for &mut (x, y, width, height) in rects { + let offset = y as u64 * *buf_stride as u64 + x as u64 * buf_format.bpp as u64; + let region = BufferImageCopy2::default() + .buffer_offset(offset) + .buffer_row_length(*buf_stride / buf_format.bpp) + .buffer_image_height(slf.height) + .image_subresource(image_subresource) + .image_offset(Offset3D { x, y, z: 0 }) + .image_extent(Extent3D { + width, + height, + depth: 1, + }); + regions.push(region); + } + let buffer_to_image = match &slf.ty { + CopyDeviceCopyType::BufferToImage { .. } => true, + CopyDeviceCopyType::ImageToBuffer { .. } => false, + _ => unreachable!(), + }; + let image_access_mask; + let image_layout; + let buffer_access_mask; + match buffer_to_image { + true => { + image_access_mask = AccessFlags2::TRANSFER_WRITE; + image_layout = ImageLayout::TRANSFER_DST_OPTIMAL; + buffer_access_mask = AccessFlags2::TRANSFER_READ; + } + false => { + image_access_mask = AccessFlags2::TRANSFER_READ; + image_layout = ImageLayout::TRANSFER_SRC_OPTIMAL; + buffer_access_mask = AccessFlags2::TRANSFER_WRITE; + } + } + let initial_image_barriers = initial_image_barriers![ + img, image_layout, image_access_mask; + ]; + let final_image_barriers = final_image_barriers![ + img, image_layout, image_access_mask; + ]; + let initial_buffer_barriers = initial_buffer_barriers![ + buf, buffer_access_mask; + ]; + let final_buffer_barriers = final_buffer_barriers![ + buf, buffer_access_mask; + ]; + let initial_dependency_info = DependencyInfo::default() + .buffer_memory_barriers(&initial_buffer_barriers) + .image_memory_barriers(&initial_image_barriers); + let final_dependency_info = DependencyInfo::default() + .buffer_memory_barriers(&final_buffer_barriers) + .image_memory_barriers(&final_image_barriers); + unsafe { + dev.cmd_pipeline_barrier2(cmd, &initial_dependency_info); + match buffer_to_image { + true => { + let copy = CopyBufferToImageInfo2::default() + .src_buffer(buf.buf) + .dst_image(img.img) + .dst_image_layout(image_layout) + .regions(®ions); + dev.cmd_copy_buffer_to_image2(cmd, ©); + } + false => { + let copy = CopyImageToBufferInfo2::default() + .src_image(img.img) + .src_image_layout(image_layout) + .dst_buffer(buf.buf) + .regions(®ions); + dev.cmd_copy_image_to_buffer2(cmd, ©); + } + } + dev.cmd_pipeline_barrier2(cmd, &final_dependency_info); + } + } + CopyDeviceCopyType::ImageToImage { src, dst } => { + let regions = &mut *slf.dev.phy.image_copy_2.borrow_mut(); + regions.clear(); + for &mut (x, y, width, height) in rects { + let region = ImageCopy2::default() + .src_subresource(image_subresource) + .src_offset(Offset3D { x, y, z: 0 }) + .dst_subresource(image_subresource) + .dst_offset(Offset3D { x, y, z: 0 }) + .extent(Extent3D { + width, + height, + depth: 1, + }); + regions.push(region); + } + use {AccessFlags2 as A, ImageLayout as L}; + let initial_barriers = initial_image_barriers![ + src, L::TRANSFER_SRC_OPTIMAL, A::TRANSFER_READ; + dst, L::TRANSFER_DST_OPTIMAL, A::TRANSFER_WRITE; + ]; + let final_barriers = final_image_barriers![ + src, L::TRANSFER_SRC_OPTIMAL, A::TRANSFER_READ; + dst, L::TRANSFER_DST_OPTIMAL, A::TRANSFER_WRITE; + ]; + let initial_dependency_info = + DependencyInfo::default().image_memory_barriers(&initial_barriers); + let final_dependency_info = + DependencyInfo::default().image_memory_barriers(&final_barriers); + let copy_image_info = CopyImageInfo2::default() + .src_image(src.img) + .src_image_layout(L::TRANSFER_SRC_OPTIMAL) + .dst_image(dst.img) + .dst_image_layout(L::TRANSFER_DST_OPTIMAL) + .regions(regions); + unsafe { + dev.cmd_pipeline_barrier2(cmd, &initial_dependency_info); + dev.cmd_copy_image2(cmd, ©_image_info); + dev.cmd_pipeline_barrier2(cmd, &final_dependency_info); + } + } + CopyDeviceCopyType::Blit { src, dst } => { + let regions = &mut *slf.dev.phy.image_blit_2.borrow_mut(); + regions.clear(); + for &mut (x, y, width, height) in rects { + let x1 = x; + let y1 = y; + let x2 = x1 + width as i32; + let y2 = y1 + height as i32; + let offsets = [ + Offset3D { x: x1, y: y1, z: 0 }, + Offset3D { x: x2, y: y2, z: 1 }, + ]; + let region = ImageBlit2::default() + .src_subresource(image_subresource) + .src_offsets(offsets) + .dst_subresource(image_subresource) + .dst_offsets(offsets); + regions.push(region); + } + use {AccessFlags2 as A, ImageLayout as L}; + let initial_barriers = initial_image_barriers![ + src, L::TRANSFER_SRC_OPTIMAL, A::TRANSFER_READ; + dst, L::TRANSFER_DST_OPTIMAL, A::TRANSFER_WRITE; + ]; + let final_barriers = final_image_barriers![ + src, L::TRANSFER_SRC_OPTIMAL, A::TRANSFER_READ; + dst, L::TRANSFER_DST_OPTIMAL, A::TRANSFER_WRITE; + ]; + let initial_dependency_info = + DependencyInfo::default().image_memory_barriers(&initial_barriers); + let final_dependency_info = + DependencyInfo::default().image_memory_barriers(&final_barriers); + let blit_image_info = BlitImageInfo2::default() + .src_image(src.img) + .src_image_layout(L::TRANSFER_SRC_OPTIMAL) + .dst_image(dst.img) + .dst_image_layout(L::TRANSFER_DST_OPTIMAL) + .regions(regions) + .filter(Filter::NEAREST); + unsafe { + dev.cmd_pipeline_barrier2(cmd, &initial_dependency_info); + dev.cmd_blit_image2(cmd, &blit_image_info); + dev.cmd_pipeline_barrier2(cmd, &final_dependency_info); + } + } + }; + unsafe { + dev.end_command_buffer(cmd) + .map_err(CopyDeviceError::EndCommandBuffer)?; + } + let mut wait_semaphore = None; + let mut wait_semaphores = ArrayVec::<_, 1>::new(); + if let Some(sync) = sync + && let Some(sync_file) = sync.get_sync_file() + { + let semaphore = match slf.dev.semaphores.pop() { + Some(s) => s, + _ => slf.dev.create_semaphore()?, + }; + semaphore.import(sync_file)?; + let info = SemaphoreSubmitInfo::default() + .semaphore(semaphore.semaphore) + .stage_mask(PipelineStageFlags2::TRANSFER); + wait_semaphores.push(info); + wait_semaphore = Some(semaphore); + } + let command_buffer_info = CommandBufferSubmitInfo::default().command_buffer(cmd); + let mut semaphore_submit_info = SemaphoreSubmitInfo::default(); + let mut submit_info = SubmitInfo2::default() + .command_buffer_infos(slice::from_ref(&command_buffer_info)) + .wait_semaphore_infos(&wait_semaphores); + let vulkan_sync = slf.dev.create_sync( + self.dev.timeline_semaphore.as_ref(), + &mut semaphore_submit_info, + &mut submit_info, + )?; + unsafe { + slf.dev + .dev + .queue_submit2( + slf.dev.queues[tt], + slice::from_ref(&submit_info), + vulkan_sync.fence(), + ) + .map_err(CopyDeviceError::SubmitCopy)?; + } + let sync = vulkan_sync.to_sync(|| slf.dev.wait_idle()); + slf.busy.set(sync.clone()); + let pending = Pending { + dev: slf.dev.clone(), + busy_id: slf.busy_id.add_fetch(1), + sync: sync.clone(), + copy: self.inner.clone(), + semaphore: wait_semaphore, + vulkan_sync, + }; + slf.dev.submissions[tt].pending.push(pending); + Ok(sync) + } +} diff --git a/src/gfx_apis/vulkan/renderer.rs b/src/gfx_apis/vulkan/renderer.rs index 9e8ceabb..9f89d3d1 100644 --- a/src/gfx_apis/vulkan/renderer.rs +++ b/src/gfx_apis/vulkan/renderer.rs @@ -1,6 +1,7 @@ mod color; mod op; mod pipeline_cache; +mod pipelines; mod paint_region; use { @@ -10,7 +11,7 @@ use { VulkanRoundedTexOp, VulkanTexOp, }, paint_region::{PaintRegion, Point, constrain_to_fb}, - pipeline_cache::{FillPipelines, OutPipelineKey, TexPipelineKey, TexPipelines}, + pipeline_cache::{FillPipelines, OutPipelineKey, TexPipelines}, crate::{ async_engine::{AsyncEngine, SpawnedFuture}, cmm::{ @@ -19,8 +20,8 @@ use { }, cpu_worker::PendingJob, gfx_api::{ - AcquireSync, AlphaMode, BufferResv, BufferResvUser, FdSync, GfxApiOpt, GfxBlendBuffer, - GfxFormat, GfxTexture, GfxWriteModifier, ReleaseSync, + AcquireSync, BufferResv, BufferResvUser, FdSync, GfxApiOpt, GfxBlendBuffer, GfxFormat, + GfxTexture, GfxWriteModifier, ReleaseSync, }, gfx_apis::vulkan::{ VulkanError, VulkanSync, VulkanTimelineSemaphore, @@ -30,9 +31,9 @@ use { descriptor::VulkanDescriptorSetLayout, descriptor_buffer::VulkanDescriptorBufferWriter, device::VulkanDevice, - eotfs::{EOTF_LINEAR, EotfExt, VulkanEotf}, + eotfs::VulkanEotf, image::{QueueFamily, QueueState, QueueTransfer, VulkanImage, VulkanImageMemory}, - pipeline::{PipelineCreateInfo, VulkanPipeline}, + pipeline::VulkanPipeline, sampler::VulkanSampler, semaphore::VulkanSemaphore, shaders::{ @@ -78,7 +79,7 @@ use { }, }, isnt::std_1::{collections::IsntHashMapExt, primitive::IsntSliceExt}, - linearize::{Linearize, LinearizeExt, StaticMap, static_map}, + linearize::{Linearize, LinearizeExt, StaticMap}, std::{ any::Any, borrow::Cow, @@ -378,261 +379,6 @@ impl VulkanDevice { } impl VulkanRenderer { - fn get_or_create_fill_pipelines( - &self, - format: vk::Format, - ) -> Result { - if let Some(pl) = self.fill_pipelines.get(&format) { - return Ok(pl); - } - let create_fill_pipeline = |src_has_alpha| { - let push_size = if self.device.descriptor_buffer.is_some() { - size_of::() - } else { - size_of::() - }; - let info = PipelineCreateInfo { - format, - vert: self.fill_vert_shader.clone(), - frag: self.fill_frag_shader.clone(), - blend: src_has_alpha, - src_has_alpha, - has_alpha_mult: false, - alpha_mode: AlphaMode::PremultipliedOptical, - // all transformations are applied in the compositor - eotf: EOTF_LINEAR, - inv_eotf: EOTF_LINEAR, - descriptor_set_layouts: Default::default(), - has_color_management_data: false, - }; - self.device.create_pipeline2(info, push_size) - }; - let fill_pipelines = Rc::new(static_map! { - TexSourceType::HasAlpha => create_fill_pipeline(true)?, - TexSourceType::Opaque => create_fill_pipeline(false)?, - }); - self.fill_pipelines.set(format, fill_pipelines.clone()); - Ok(fill_pipelines) - } - - fn get_or_create_rounded_fill_pipelines( - &self, - format: vk::Format, - ) -> Result { - if let Some(pl) = self.rounded_fill_pipelines.get(&format) { - return Ok(pl); - } - let create_pipeline = |src_has_alpha| { - let push_size = if self.device.descriptor_buffer.is_some() { - size_of::() - } else { - size_of::() - }; - let info = PipelineCreateInfo { - format, - vert: self.rounded_fill_vert_shader.clone(), - frag: self.rounded_fill_frag_shader.clone(), - blend: src_has_alpha, - src_has_alpha, - has_alpha_mult: false, - alpha_mode: AlphaMode::PremultipliedOptical, - eotf: EOTF_LINEAR, - inv_eotf: EOTF_LINEAR, - descriptor_set_layouts: Default::default(), - has_color_management_data: false, - }; - self.device.create_pipeline2(info, push_size) - }; - let pipelines = Rc::new(static_map! { - TexSourceType::HasAlpha => create_pipeline(true)?, - TexSourceType::Opaque => create_pipeline(false)?, - }); - self.rounded_fill_pipelines.set(format, pipelines.clone()); - Ok(pipelines) - } - - fn get_or_create_rounded_tex_pipelines( - &self, - format: vk::Format, - target_cd: &ColorDescription, - ) -> Rc { - let eotf = target_cd.eotf.to_vulkan(); - let pipelines = &self.rounded_tex_pipelines[eotf]; - match pipelines.get(&format) { - Some(pl) => pl, - _ => { - let pl = Rc::new(TexPipelines { - format, - eotf, - pipelines: Default::default(), - }); - pipelines.set(format, pl.clone()); - pl - } - } - } - - fn get_or_create_rounded_tex_pipeline( - &self, - pipelines: &TexPipelines, - tex_cd: &ColorDescription, - tex_copy_type: TexCopyType, - tex_source_type: TexSourceType, - mut tex_alpha_mode: AlphaMode, - has_color_management_data: bool, - ) -> Result, VulkanError> { - if tex_source_type == TexSourceType::Opaque { - tex_alpha_mode = AlphaMode::PremultipliedElectrical; - } - let key = TexPipelineKey { - tex_copy_type, - tex_source_type, - tex_alpha_mode, - eotf: tex_cd.eotf.to_vulkan(), - has_color_management_data, - }; - if let Some(pl) = pipelines.pipelines.get(&key) { - return Ok(pl); - } - let has_alpha_mult = match tex_copy_type { - TexCopyType::Identity => false, - TexCopyType::Multiply => true, - }; - let push_size = if self.device.descriptor_buffer.is_some() { - size_of::() - } else { - size_of::() - }; - let info = PipelineCreateInfo { - format: pipelines.format, - vert: self.rounded_tex_vert_shader.clone(), - frag: self.rounded_tex_frag_shader.clone(), - blend: true, // always blend since corners are transparent - src_has_alpha: true, // rounding makes everything have alpha - has_alpha_mult, - alpha_mode: key.tex_alpha_mode, - eotf: key.eotf.to_vulkan(), - inv_eotf: pipelines.eotf.to_vulkan(), - descriptor_set_layouts: self.tex_descriptor_set_layouts.clone(), - has_color_management_data, - }; - let pl = self.device.create_pipeline2(info, push_size)?; - pipelines.pipelines.set(key, pl.clone()); - Ok(pl) - } - - fn get_or_create_tex_pipelines( - &self, - format: vk::Format, - target_cd: &ColorDescription, - ) -> Rc { - let eotf = target_cd.eotf.to_vulkan(); - let pipelines = &self.tex_pipelines[eotf]; - match pipelines.get(&format) { - Some(pl) => pl, - _ => { - let pl = Rc::new(TexPipelines { - format, - eotf, - pipelines: Default::default(), - }); - pipelines.set(format, pl.clone()); - pl - } - } - } - - fn get_or_create_tex_pipeline( - &self, - pipelines: &TexPipelines, - tex_cd: &ColorDescription, - tex_copy_type: TexCopyType, - tex_source_type: TexSourceType, - mut tex_alpha_mode: AlphaMode, - has_color_management_data: bool, - ) -> Result, VulkanError> { - if tex_source_type == TexSourceType::Opaque { - tex_alpha_mode = AlphaMode::PremultipliedElectrical; - } - let key = TexPipelineKey { - tex_copy_type, - tex_source_type, - tex_alpha_mode, - eotf: tex_cd.eotf.to_vulkan(), - has_color_management_data, - }; - if let Some(pl) = pipelines.pipelines.get(&key) { - return Ok(pl); - } - let src_has_alpha = match tex_source_type { - TexSourceType::Opaque => false, - TexSourceType::HasAlpha => true, - }; - let has_alpha_mult = match tex_copy_type { - TexCopyType::Identity => false, - TexCopyType::Multiply => true, - }; - let push_size = if self.device.descriptor_buffer.is_some() { - size_of::() - } else { - size_of::() - }; - let info = PipelineCreateInfo { - format: pipelines.format, - vert: self.tex_vert_shader.clone(), - frag: self.tex_frag_shader.clone(), - blend: src_has_alpha || has_alpha_mult, - src_has_alpha, - has_alpha_mult, - alpha_mode: key.tex_alpha_mode, - eotf: key.eotf.to_vulkan(), - inv_eotf: pipelines.eotf.to_vulkan(), - descriptor_set_layouts: self.tex_descriptor_set_layouts.clone(), - has_color_management_data, - }; - let pl = self.device.create_pipeline2(info, push_size)?; - pipelines.pipelines.set(key, pl.clone()); - Ok(pl) - } - - fn get_or_create_out_pipeline( - &self, - format: vk::Format, - bb_cd: &ColorDescription, - fb_cd: &ColorDescription, - has_color_management_data: bool, - ) -> Result, VulkanError> { - let key = OutPipelineKey { - format, - eotf: bb_cd.eotf.to_vulkan(), - has_color_management_data, - }; - let fb_eotf = fb_cd.eotf.to_vulkan(); - let pipelines = &self.out_pipelines[fb_eotf]; - if let Some(pl) = pipelines.get(&key) { - return Ok(pl); - } - let mut descriptor_set_layouts = ArrayVec::new(); - descriptor_set_layouts.push(self.out_descriptor_set_layout.clone().unwrap()); - let out = self - .device - .create_pipeline::(PipelineCreateInfo { - format: key.format, - vert: self.out_vert_shader.clone().unwrap(), - frag: self.out_frag_shader.clone().unwrap(), - blend: false, - src_has_alpha: true, - has_alpha_mult: false, - alpha_mode: AlphaMode::PremultipliedElectrical, - eotf: key.eotf.to_vulkan(), - inv_eotf: fb_eotf.to_vulkan(), - descriptor_set_layouts, - has_color_management_data, - })?; - pipelines.set(key, out.clone()); - Ok(out) - } - pub(super) fn allocate_point(&self) -> u64 { self.last_point.fetch_add(1) + 1 } diff --git a/src/gfx_apis/vulkan/renderer/pipelines.rs b/src/gfx_apis/vulkan/renderer/pipelines.rs new file mode 100644 index 00000000..29b7429a --- /dev/null +++ b/src/gfx_apis/vulkan/renderer/pipelines.rs @@ -0,0 +1,282 @@ +use { + super::{ + VulkanRenderer, + op::{TexCopyType, TexSourceType}, + pipeline_cache::{FillPipelines, OutPipelineKey, TexPipelineKey, TexPipelines}, + }, + crate::{ + cmm::cmm_description::ColorDescription, + gfx_api::AlphaMode, + gfx_apis::vulkan::{ + VulkanError, + eotfs::{EOTF_LINEAR, EotfExt}, + pipeline::{PipelineCreateInfo, VulkanPipeline}, + shaders::{ + FillPushConstants, LegacyFillPushConstants, LegacyRoundedFillPushConstants, + LegacyRoundedTexPushConstants, LegacyTexPushConstants, OutPushConstants, + RoundedFillPushConstants, RoundedTexPushConstants, TexPushConstants, + }, + }, + }, + arrayvec::ArrayVec, + ash::vk, + linearize::static_map, + std::{mem::size_of, rc::Rc}, +}; + +impl VulkanRenderer { + pub(super) fn get_or_create_fill_pipelines( + &self, + format: vk::Format, + ) -> Result { + if let Some(pl) = self.fill_pipelines.get(&format) { + return Ok(pl); + } + let create_fill_pipeline = |src_has_alpha| { + let push_size = if self.device.descriptor_buffer.is_some() { + size_of::() + } else { + size_of::() + }; + let info = PipelineCreateInfo { + format, + vert: self.fill_vert_shader.clone(), + frag: self.fill_frag_shader.clone(), + blend: src_has_alpha, + src_has_alpha, + has_alpha_mult: false, + alpha_mode: AlphaMode::PremultipliedOptical, + // all transformations are applied in the compositor + eotf: EOTF_LINEAR, + inv_eotf: EOTF_LINEAR, + descriptor_set_layouts: Default::default(), + has_color_management_data: false, + }; + self.device.create_pipeline2(info, push_size) + }; + let fill_pipelines = Rc::new(static_map! { + TexSourceType::HasAlpha => create_fill_pipeline(true)?, + TexSourceType::Opaque => create_fill_pipeline(false)?, + }); + self.fill_pipelines.set(format, fill_pipelines.clone()); + Ok(fill_pipelines) + } + + pub(super) fn get_or_create_rounded_fill_pipelines( + &self, + format: vk::Format, + ) -> Result { + if let Some(pl) = self.rounded_fill_pipelines.get(&format) { + return Ok(pl); + } + let create_pipeline = |src_has_alpha| { + let push_size = if self.device.descriptor_buffer.is_some() { + size_of::() + } else { + size_of::() + }; + let info = PipelineCreateInfo { + format, + vert: self.rounded_fill_vert_shader.clone(), + frag: self.rounded_fill_frag_shader.clone(), + blend: src_has_alpha, + src_has_alpha, + has_alpha_mult: false, + alpha_mode: AlphaMode::PremultipliedOptical, + eotf: EOTF_LINEAR, + inv_eotf: EOTF_LINEAR, + descriptor_set_layouts: Default::default(), + has_color_management_data: false, + }; + self.device.create_pipeline2(info, push_size) + }; + let pipelines = Rc::new(static_map! { + TexSourceType::HasAlpha => create_pipeline(true)?, + TexSourceType::Opaque => create_pipeline(false)?, + }); + self.rounded_fill_pipelines.set(format, pipelines.clone()); + Ok(pipelines) + } + + pub(super) fn get_or_create_rounded_tex_pipelines( + &self, + format: vk::Format, + target_cd: &ColorDescription, + ) -> Rc { + let eotf = target_cd.eotf.to_vulkan(); + let pipelines = &self.rounded_tex_pipelines[eotf]; + match pipelines.get(&format) { + Some(pl) => pl, + _ => { + let pl = Rc::new(TexPipelines { + format, + eotf, + pipelines: Default::default(), + }); + pipelines.set(format, pl.clone()); + pl + } + } + } + + pub(super) fn get_or_create_rounded_tex_pipeline( + &self, + pipelines: &TexPipelines, + tex_cd: &ColorDescription, + tex_copy_type: TexCopyType, + tex_source_type: TexSourceType, + mut tex_alpha_mode: AlphaMode, + has_color_management_data: bool, + ) -> Result, VulkanError> { + if tex_source_type == TexSourceType::Opaque { + tex_alpha_mode = AlphaMode::PremultipliedElectrical; + } + let key = TexPipelineKey { + tex_copy_type, + tex_source_type, + tex_alpha_mode, + eotf: tex_cd.eotf.to_vulkan(), + has_color_management_data, + }; + if let Some(pl) = pipelines.pipelines.get(&key) { + return Ok(pl); + } + let has_alpha_mult = match tex_copy_type { + TexCopyType::Identity => false, + TexCopyType::Multiply => true, + }; + let push_size = if self.device.descriptor_buffer.is_some() { + size_of::() + } else { + size_of::() + }; + let info = PipelineCreateInfo { + format: pipelines.format, + vert: self.rounded_tex_vert_shader.clone(), + frag: self.rounded_tex_frag_shader.clone(), + blend: true, + src_has_alpha: true, + has_alpha_mult, + alpha_mode: key.tex_alpha_mode, + eotf: key.eotf.to_vulkan(), + inv_eotf: pipelines.eotf.to_vulkan(), + descriptor_set_layouts: self.tex_descriptor_set_layouts.clone(), + has_color_management_data, + }; + let pl = self.device.create_pipeline2(info, push_size)?; + pipelines.pipelines.set(key, pl.clone()); + Ok(pl) + } + + pub(super) fn get_or_create_tex_pipelines( + &self, + format: vk::Format, + target_cd: &ColorDescription, + ) -> Rc { + let eotf = target_cd.eotf.to_vulkan(); + let pipelines = &self.tex_pipelines[eotf]; + match pipelines.get(&format) { + Some(pl) => pl, + _ => { + let pl = Rc::new(TexPipelines { + format, + eotf, + pipelines: Default::default(), + }); + pipelines.set(format, pl.clone()); + pl + } + } + } + + pub(super) fn get_or_create_tex_pipeline( + &self, + pipelines: &TexPipelines, + tex_cd: &ColorDescription, + tex_copy_type: TexCopyType, + tex_source_type: TexSourceType, + mut tex_alpha_mode: AlphaMode, + has_color_management_data: bool, + ) -> Result, VulkanError> { + if tex_source_type == TexSourceType::Opaque { + tex_alpha_mode = AlphaMode::PremultipliedElectrical; + } + let key = TexPipelineKey { + tex_copy_type, + tex_source_type, + tex_alpha_mode, + eotf: tex_cd.eotf.to_vulkan(), + has_color_management_data, + }; + if let Some(pl) = pipelines.pipelines.get(&key) { + return Ok(pl); + } + let src_has_alpha = match tex_source_type { + TexSourceType::Opaque => false, + TexSourceType::HasAlpha => true, + }; + let has_alpha_mult = match tex_copy_type { + TexCopyType::Identity => false, + TexCopyType::Multiply => true, + }; + let push_size = if self.device.descriptor_buffer.is_some() { + size_of::() + } else { + size_of::() + }; + let info = PipelineCreateInfo { + format: pipelines.format, + vert: self.tex_vert_shader.clone(), + frag: self.tex_frag_shader.clone(), + blend: src_has_alpha || has_alpha_mult, + src_has_alpha, + has_alpha_mult, + alpha_mode: key.tex_alpha_mode, + eotf: key.eotf.to_vulkan(), + inv_eotf: pipelines.eotf.to_vulkan(), + descriptor_set_layouts: self.tex_descriptor_set_layouts.clone(), + has_color_management_data, + }; + let pl = self.device.create_pipeline2(info, push_size)?; + pipelines.pipelines.set(key, pl.clone()); + Ok(pl) + } + + pub(super) fn get_or_create_out_pipeline( + &self, + format: vk::Format, + bb_cd: &ColorDescription, + fb_cd: &ColorDescription, + has_color_management_data: bool, + ) -> Result, VulkanError> { + let key = OutPipelineKey { + format, + eotf: bb_cd.eotf.to_vulkan(), + has_color_management_data, + }; + let fb_eotf = fb_cd.eotf.to_vulkan(); + let pipelines = &self.out_pipelines[fb_eotf]; + if let Some(pl) = pipelines.get(&key) { + return Ok(pl); + } + let mut descriptor_set_layouts = ArrayVec::new(); + descriptor_set_layouts.push(self.out_descriptor_set_layout.clone().unwrap()); + let out = self + .device + .create_pipeline::(PipelineCreateInfo { + format: key.format, + vert: self.out_vert_shader.clone().unwrap(), + frag: self.out_frag_shader.clone().unwrap(), + blend: false, + src_has_alpha: true, + has_alpha_mult: false, + alpha_mode: AlphaMode::PremultipliedElectrical, + eotf: key.eotf.to_vulkan(), + inv_eotf: fb_eotf.to_vulkan(), + descriptor_set_layouts, + has_color_management_data, + })?; + pipelines.set(key, out.clone()); + Ok(out) + } +} diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index 1a02f4d3..bbf03945 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -2,17 +2,20 @@ mod event_handling; mod device_handler; pub mod ext_transient_seat_manager_v1; pub mod ext_transient_seat_v1; +mod focus; mod gesture_owner; mod kb_owner; mod pointer_owner; mod position_hint; mod seat_object; +mod selection; pub mod tablet; pub mod text_input; mod touch_owner; pub mod wl_keyboard; pub mod wl_pointer; pub mod wl_touch; +mod window_management; pub mod wp_pointer_warp_v1; pub mod zwp_pointer_constraints_v1; pub mod zwp_pointer_gesture_hold_v1; @@ -38,16 +41,12 @@ use { ifs::{ ext_idle_notification_v1::ExtIdleNotificationV1, data_transfer::{ - self, DynDataSource, TransferError, TransferLocation, + self, DynDataSource, TransferError, data_control::{DataControlDeviceId, DynDataControlDevice}, - offer_source_to_regular_client, - wl_data_device::{ClipboardTransfer, WlDataDevice}, + wl_data_device::WlDataDevice, wl_data_source::WlDataSource, - x_data_device::{XClipboardTransfer, XTransferDevice, XTransferDeviceId, XPrimarySelectionTransfer}, - zwp_primary_selection_device_v1::{ - PrimarySelectionTransfer, ZwpPrimarySelectionDeviceV1, - }, - zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1, + x_data_device::{XTransferDevice, XTransferDeviceId}, + zwp_primary_selection_device_v1::ZwpPrimarySelectionDeviceV1, }, wl_output::WlOutputGlobal, wl_seat::{ @@ -75,7 +74,6 @@ use { dnd_icon::DndIcon, tray::{DynTrayItem, TrayItemId}, xdg_surface::{xdg_popup::XdgPopup, xdg_toplevel::ResizeEdges}, - zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, }, xdg_toplevel_drag_v1::XdgToplevelDragV1, }, @@ -86,10 +84,8 @@ use { rect::Rect, state::{DeviceHandlerData, State}, tree::{ - ChangeGroupAction, ContainerNode, ContainerSplit, Direction, FoundNode, Node, NodeId, - NodeLayer, NodeLayerLink, NodeLocation, OutputNode, StackedNode, ToplevelNode, - WorkspaceNode, generic_node_visitor, toplevel_create_split, toplevel_parent_container, - toplevel_set_floating, toplevel_set_workspace, + FoundNode, Node, NodeId, NodeLocation, OutputNode, ToplevelNode, WorkspaceNode, + generic_node_visitor, toplevel_set_workspace, }, utils::{ asyncevent::AsyncEvent, @@ -97,9 +93,9 @@ use { clonecell::CloneCell, copyhashmap::CopyHashMap, event_listener::{EventListener, EventSource}, - linkedlist::{LinkedList, LinkedNode, NodeRef}, + linkedlist::{LinkedList, LinkedNode}, numcell::NumCell, - rc_eq::{rc_eq, rc_weak_eq}, + rc_eq::rc_eq, smallmap::SmallMap, static_text::StaticText, }, @@ -117,13 +113,12 @@ use { }, kbvm::Keycode, linearize::Linearize, - run_on_drop::on_drop, smallvec::SmallVec, std::{ cell::{Cell, RefCell}, collections::hash_map::Entry, mem, - ops::{Deref, DerefMut}, + ops::DerefMut, rc::{Rc, Weak}, }, thiserror::Error, @@ -713,104 +708,6 @@ impl WlSeatGlobal { self.kb_owner.ungrab(self); } - pub fn kb_parent_container(&self) -> Option> { - if let Some(tl) = self.keyboard_node.get().node_toplevel() { - return toplevel_parent_container(&*tl); - } - None - } - - pub fn get_mono(&self) -> Option { - self.kb_parent_container().map(|c| c.mono_child.is_some()) - } - - pub fn get_split(&self) -> Option { - self.kb_parent_container().map(|c| c.split.get()) - } - - pub fn set_mono(&self, mono: bool) { - if let Some(tl) = self.keyboard_node.get().node_toplevel() - && let Some(parent) = tl.tl_data().parent.get() - && let Some(container) = parent.node_into_container() - { - let node = if mono { Some(tl.deref()) } else { None }; - container.set_mono(node); - } - } - - pub fn set_split(&self, axis: ContainerSplit) { - if let Some(c) = self.kb_parent_container() { - c.set_split(axis); - } - } - - pub fn create_split(&self, axis: ContainerSplit) { - let tl = match self.keyboard_node.get().node_toplevel() { - Some(tl) => tl, - _ => return, - }; - toplevel_create_split(&self.state, tl, axis); - } - - pub fn toggle_tab(&self) { - if let Some(c) = self.kb_parent_container() { - c.change_group(ChangeGroupAction::ToggleTab); - } - } - - pub fn make_group(&self, axis: ContainerSplit, ephemeral: bool) { - if let Some(c) = self.kb_parent_container() { - c.make_group(axis, ephemeral); - } - } - - pub fn change_group_opposite(&self) { - if let Some(c) = self.kb_parent_container() { - c.change_group(ChangeGroupAction::Opposite); - } - } - - pub fn equalize(&self, recursive: bool) { - if let Some(c) = self.kb_parent_container() { - if recursive { - c.equalize_recursive(); - } else { - c.equalize(); - } - } - } - - pub fn move_tab(&self, right: bool) { - if let Some(c) = self.kb_parent_container() { - c.move_tab(right); - } - } - - pub fn focus_parent(self: &Rc) { - if let Some(tl) = self.keyboard_node.get().node_toplevel() - && let Some(parent) = tl.tl_data().parent.get() - && let Some(tl) = parent.node_toplevel() - { - self.focus_node(tl); - self.maybe_schedule_warp_mouse_to_focus(); - } - } - - pub fn get_floating(self: &Rc) -> Option { - match self.keyboard_node.get().node_toplevel() { - Some(tl) => Some(tl.tl_data().parent_is_float.get()), - _ => None, - } - } - - pub fn set_floating(self: &Rc, floating: bool) { - let tl = match self.keyboard_node.get().node_toplevel() { - Some(tl) => tl, - _ => return, - }; - toplevel_set_floating(&self.state, tl, floating); - } - pub fn get_rate(&self) -> (i32, i32) { self.repeat_rate.get() } @@ -834,519 +731,6 @@ impl WlSeatGlobal { } } - pub fn close(self: &Rc) { - let kb_node = self.keyboard_node.get(); - if let Some(tl) = kb_node.node_toplevel() { - tl.tl_close(); - } - } - - pub fn move_focus(self: &Rc, direction: Direction) { - let tl = match self.keyboard_node.get().node_toplevel() { - Some(tl) => tl, - _ => { - if let Some(ws) = self.keyboard_node.get().node_into_workspace() - && let Some(target) = self - .state - .find_output_in_direction(&ws.output.get(), direction) - { - target.take_keyboard_navigation_focus(self, direction); - self.maybe_schedule_warp_mouse_to_focus(); - } - return; - } - }; - if direction == Direction::Down && tl.node_is_container() { - tl.node_do_focus(self, direction); - } else { - let data = tl.tl_data(); - if data.is_fullscreen.get() - && let Some(output) = data.output_opt() - && let Some(target) = self.state.find_output_in_direction(&output, direction) - { - target.take_keyboard_navigation_focus(self, direction); - } else if let Some(p) = data.parent.get() - && let Some(c) = p.node_into_container() - { - c.move_focus_from_child(self, tl.deref(), direction); - } else if let Some(float) = data.float.get() { - let ws = float.workspace.get(); - let floats: Vec<_> = ws - .stacked - .iter() - .filter_map(|node| (*node).clone().node_into_float()) - .filter(|f| f.child.get().is_some()) - .collect(); - if let Some(pos) = floats.iter().position(|f| f.id == float.id) { - let target = match direction { - Direction::Left | Direction::Down => { - if pos == 0 { - floats.last() - } else { - floats.get(pos - 1) - } - } - _ => { - if pos + 1 >= floats.len() { - floats.first() - } else { - floats.get(pos + 1) - } - } - }; - if let Some(f) = target - && f.id != float.id - { - f.clone().node_do_focus(self, Direction::Unspecified); - } - } - } - } - self.maybe_schedule_warp_mouse_to_focus(); - } - - pub fn maybe_schedule_warp_mouse_to_focus(self: &Rc) { - if self.mouse_follows_focus() { - self.warp_mouse_to_focus_skip_target_check.set(true); - self.schedule_warp_mouse_to_focus(); - } - } - - pub fn schedule_warp_mouse_to_focus(self: &Rc) { - if !self.warp_mouse_to_focus_scheduled.replace(true) { - self.state.pending_warp_mouse_to_focus.push(self.clone()); - } - } - - pub fn move_focused(self: &Rc, direction: Direction) { - let kb_node = self.keyboard_node.get(); - let Some(tl) = kb_node.node_toplevel() else { - if let Some(ws) = self.keyboard_node.get().node_into_workspace() - && let Some(target) = self - .state - .find_output_in_direction(&ws.output.get(), direction) - { - self.state.move_ws_to_output(&ws, &target); - } - return; - }; - let data = tl.tl_data(); - if data.is_fullscreen.get() - && let Some(output) = data.output_opt() - && let Some(target) = self.state.find_output_in_direction(&output, direction) - { - let ws = target.ensure_workspace(); - toplevel_set_workspace(&self.state, tl, &ws); - self.maybe_schedule_warp_mouse_to_focus(); - } else if let Some(parent) = data.parent.get() - && let Some(c) = parent.node_into_container() - { - c.move_child(tl, direction); - self.maybe_schedule_warp_mouse_to_focus(); - } else if let Some(float) = data.float.get() { - float.move_by_direction(direction); - self.maybe_schedule_warp_mouse_to_focus(); - } - } - - pub fn get_last_focus_on_workspace(&self, ws: &WorkspaceNode) -> Option> { - let mut node = self.focus_history.last()?; - loop { - if let Some(node) = node.node.upgrade() - && let Some(NodeLocation::Workspace(_, new)) = node.node_location() - && new == ws.id - { - return Some(node); - } - node = node.prev()?; - } - } - - fn get_focus_history( - &self, - next: impl Fn(&NodeRef) -> Option>, - first: impl FnOnce(&LinkedList) -> Option>, - ) -> Option<(Rc, bool)> { - let original = self.keyboard_node.get(); - let mut output = None; - let mut workspace = None; - if let Some(old) = original.node_location() { - match old { - NodeLocation::Workspace(o, w) => { - workspace = Some(w); - output = Some(o); - } - NodeLocation::Output(o) => { - output = Some(o); - } - } - } - if (output.is_none() || workspace.is_none()) - && let Some(old) = self.last_focus_location.get() - { - match old { - NodeLocation::Workspace(o, w) => { - workspace = workspace.or(Some(w)); - output = output.or(Some(o)); - } - NodeLocation::Output(o) => { - output = output.or(Some(o)); - } - } - } - if workspace.is_none() - && let Some(output) = original.node_output() - && let Some(ws) = output.workspace.get() - { - workspace = Some(ws.id); - } - let matches = |node: &FocusHistoryData| { - let visible = node.visible.get(); - if self.focus_history_visible_only.get() && !visible { - return None; - } - let node = node.node.upgrade()?; - if self.focus_history_same_workspace.get() { - let new = node.node_location()?; - let o = match new { - NodeLocation::Workspace(o, w) => { - if workspace != Some(w) { - return None; - } - o - } - NodeLocation::Output(o) => o, - }; - if output != Some(o) { - return None; - } - } - Some((node, visible)) - }; - let node = original.node_seat_state().get_focus_history(self); - if let Some(mut node) = node { - loop { - node = match next(&node) { - Some(n) => n, - _ => break, - }; - if let Some(matches) = matches(&node) { - return Some(matches); - } - } - } - let mut node = first(&self.focus_history)?; - loop { - if rc_weak_eq(&original, &node.node) { - return None; - } - if let Some(matches) = matches(&node) { - return Some(matches); - } - node = next(&node)?; - } - } - - fn focus_history( - self: &Rc, - next: impl Fn(&NodeRef) -> Option>, - first: impl FnOnce(&LinkedList) -> Option>, - ) { - let Some((node, visible)) = self.get_focus_history(next, first) else { - return; - }; - self.focus_history_rotate.fetch_add(1); - let _reset = on_drop(|| { - self.focus_history_rotate.fetch_sub(1); - }); - if !visible { - node.clone().node_make_visible(); - if !node.node_visible() { - return; - } - } - self.focus_node(node); - self.maybe_schedule_warp_mouse_to_focus(); - } - - pub fn focus_prev(self: &Rc) { - self.focus_history(|s| s.prev(), |l| l.last()); - } - - pub fn focus_next(self: &Rc) { - self.focus_history(|s| s.next(), |l| l.first()); - } - - pub fn focus_history_set_visible(&self, visible: bool) { - self.focus_history_visible_only.set(visible); - } - - pub fn focus_history_set_same_workspace(&self, same_workspace: bool) { - self.focus_history_same_workspace.set(same_workspace); - } - - fn focus_layer_rel( - self: &Rc, - next_layer: impl Fn(NodeLayer) -> NodeLayer, - layer_node_next: impl Fn( - &NodeRef>, - ) -> Option>>, - stacked_node_next: impl Fn( - &NodeRef>, - ) -> Option>>, - layer_list_iter: impl Fn(&LinkedList>) -> LI, - stacked_list_iter: impl Fn(&LinkedList>) -> SI, - ) where - LI: Iterator>>, - SI: Iterator>>, - { - fn node_viable(n: &(impl Node + ?Sized)) -> bool { - n.node_visible() && n.node_accepts_focus() - } - - let current = self.keyboard_node.get(); - let Some(output) = current.node_output() else { - return; - }; - let current_layer = current.node_layer(); - match ¤t_layer { - NodeLayerLink::Layer0(l) - | NodeLayerLink::Layer1(l) - | NodeLayerLink::Layer2(l) - | NodeLayerLink::Layer3(l) => { - if let Some(n) = layer_node_next(l) - && node_viable(&**n) - { - n.deref() - .clone() - .node_do_focus(self, Direction::Unspecified); - self.maybe_schedule_warp_mouse_to_focus(); - return; - } - } - NodeLayerLink::Stacked(l) | NodeLayerLink::StackedAboveLayers(l) => { - if let Some(n) = stacked_node_next(l) - && node_viable(&**n) - && n.node_output().map(|o| o.id) == Some(output.id) - { - n.deref() - .clone() - .node_do_focus(self, Direction::Unspecified); - self.maybe_schedule_warp_mouse_to_focus(); - return; - } - } - NodeLayerLink::Display => {} - NodeLayerLink::Output => {} - NodeLayerLink::Workspace => {} - NodeLayerLink::Tiled => {} - NodeLayerLink::Fullscreen => {} - NodeLayerLink::Lock => {} - NodeLayerLink::InputMethod => {} - } - let handle_layer_shell = |l: &LinkedList>| { - for n in layer_list_iter(l) { - if node_viable(&**n) { - return Some(n.deref().clone() as Rc); - } - } - None - }; - let handle_stacked = |l: &LinkedList>| { - for n in stacked_list_iter(l) { - if node_viable(&**n) && n.node_output().map(|o| o.id) == Some(output.id) { - return Some(n.deref().clone() as Rc); - } - } - None - }; - let ws = output.workspace.get(); - let first = next_layer(current_layer.layer()); - let mut layer = first; - loop { - let node = match layer { - NodeLayer::Display => None, - NodeLayer::Layer0 => handle_layer_shell(&output.layers[0]), - NodeLayer::Layer1 => handle_layer_shell(&output.layers[1]), - NodeLayer::Output => None, - NodeLayer::Workspace => { - if let Some(ws) = &ws - && ws.container_visible() - { - self.focus_node(ws.clone()); - self.maybe_schedule_warp_mouse_to_focus(); - return; - } - None - } - NodeLayer::Tiled => ws - .as_ref() - .and_then(|w| w.container.get()) - .map(|n| n as Rc), - NodeLayer::Fullscreen => ws - .as_ref() - .and_then(|w| w.fullscreen.get()) - .map(|n| n as Rc), - NodeLayer::Stacked => handle_stacked(&self.state.root.stacked), - NodeLayer::Layer2 => handle_layer_shell(&output.layers[2]), - NodeLayer::Layer3 => handle_layer_shell(&output.layers[3]), - NodeLayer::StackedAboveLayers => { - handle_stacked(&self.state.root.stacked_above_layers) - } - NodeLayer::Lock => None, - NodeLayer::InputMethod => None, - }; - if let Some(n) = node { - if node_viable(&*n) { - n.node_do_focus(self, Direction::Unspecified); - self.maybe_schedule_warp_mouse_to_focus(); - return; - } - } - layer = next_layer(layer); - if layer == first { - return; - } - } - } - - pub fn focus_layer_below(self: &Rc) { - self.focus_layer_rel( - |l| l.prev(), - |n| n.prev(), - |n| n.prev(), - |l| l.rev_iter(), - |l| l.rev_iter(), - ); - } - - pub fn focus_layer_above(self: &Rc) { - self.focus_layer_rel( - |l| l.next(), - |n| n.next(), - |n| n.next(), - |l| l.iter(), - |l| l.iter(), - ); - } - - pub fn toggle_focus_float_tiled(self: &Rc) { - let current = self.keyboard_node.get(); - match current.node_layer().layer() { - NodeLayer::Tiled | NodeLayer::Fullscreen => self.focus_floats(), - _ => self.focus_tiles(), - } - self.maybe_schedule_warp_mouse_to_focus(); - } - - pub fn focus_floats(self: &Rc) { - let current = self.keyboard_node.get(); - if current.node_layer().layer() == NodeLayer::Stacked { - return; - } - let Some(output) = current.node_output() else { - return; - }; - let Some(ws) = output.workspace.get() else { - return; - }; - if let Some(child) = ws - .stacked - .rev_iter() - .filter_map(|node| (*node).clone().node_into_float()) - .find_map(|float| float.child.get()) - { - child.node_do_focus(self, Direction::Unspecified); - } - } - - pub fn focus_tiles(self: &Rc) { - let current = self.keyboard_node.get(); - if matches!( - current.node_layer().layer(), - NodeLayer::Tiled | NodeLayer::Fullscreen, - ) { - return; - } - let Some(output) = current.node_output() else { - return; - }; - let Some(ws) = output.workspace.get() else { - return; - }; - let node = match ws.fullscreen.get() { - Some(fs) => fs as Rc, - _ => match ws.container.get() { - Some(c) => c, - _ => return, - }, - }; - if node.node_visible() && node.node_accepts_focus() { - node.node_do_focus(self, Direction::Unspecified); - self.maybe_schedule_warp_mouse_to_focus(); - } - } - - fn set_selection_( - self: &Rc, - field: &CloneCell>>, - src: Option>, - location: TransferLocation, - ) -> Result<(), WlSeatError> - where - T: data_transfer::IterableTransferVtable, - X: data_transfer::TransferVtable, - S: DynDataSource, - { - if let (Some(new), Some(old)) = (&src, &field.get()) - && new.source_data().id == old.source_data().id - { - return Ok(()); - } - if let Some(new) = &src { - data_transfer::attach_seat(&**new, self, data_transfer::Role::Selection)?; - } - let src_dyn = src.clone().map(|s| s as Rc); - if let Some(old) = field.set(src_dyn) { - old.detach_seat(self); - } - if let Some(client) = self.keyboard_node.get().node_client() { - self.offer_selection_to_client::(src.clone().map(|v| v as Rc<_>), &client); - // client.flush(); - } - let dyn_source = src.map(|s| s as Rc); - for dd in self.data_control_devices.lock().values() { - dd.clone().handle_new_source(location, dyn_source.clone()); - } - Ok(()) - } - - fn offer_selection_to_client( - &self, - selection: Option>, - client: &Rc, - ) where - T: data_transfer::IterableTransferVtable, - X: data_transfer::TransferVtable, - { - if let Some(src) = &selection { - src.cancel_unprivileged_offers(); - } - if client.is_xwayland { - self.for_each_x_data_device(|dd| match &selection { - Some(src) => src.clone().offer_to_x(&dd), - _ => X::send_selection(&dd, None), - }); - } else { - match selection { - Some(src) => offer_source_to_regular_client::(src, client), - _ => T::for_each_device(self, client.id, |device| { - T::send_selection(device, None); - }), - } - } - } - pub fn start_drag( self: &Rc, origin: &Rc, @@ -1398,88 +782,6 @@ impl WlSeatGlobal { self.pointer_owner.cancel_dnd(self); } - pub fn unset_selection(self: &Rc) { - let _ = self.set_wl_data_source_selection(None, None); - } - - pub fn set_wl_data_source_selection( - self: &Rc, - selection: Option>, - serial: Option, - ) -> Result<(), WlSeatError> { - if let Some(serial) = serial { - self.selection_serial.set(serial); - } - if let Some(selection) = &selection - && selection.toplevel_drag.is_some() - { - return Err(WlSeatError::OfferHasDrag); - } - self.set_selection(selection) - } - - pub fn set_selection( - self: &Rc, - selection: Option>, - ) -> Result<(), WlSeatError> { - self.set_selection_::( - &self.selection, - selection, - TransferLocation::Clipboard, - ) - } - - pub fn get_selection(&self) -> Option> { - self.selection.get() - } - - pub fn may_modify_selection(&self, client: &Rc, serial: u64) -> bool { - if serial < self.selection_serial.get() { - return false; - } - self.keyboard_node.get().node_client_id() == Some(client.id) - } - - pub fn may_modify_primary_selection(&self, client: &Rc, serial: Option) -> bool { - if let Some(serial) = serial - && serial < self.primary_selection_serial.get() - { - return false; - } - self.keyboard_node.get().node_client_id() == Some(client.id) - || self.pointer_node().and_then(|n| n.node_client_id()) == Some(client.id) - } - - pub fn unset_primary_selection(self: &Rc) { - let _ = self.set_zwp_primary_selection(None, None); - } - - pub fn set_zwp_primary_selection( - self: &Rc, - selection: Option>, - serial: Option, - ) -> Result<(), WlSeatError> { - if let Some(serial) = serial { - self.primary_selection_serial.set(serial); - } - self.set_primary_selection(selection) - } - - pub fn set_primary_selection( - self: &Rc, - selection: Option>, - ) -> Result<(), WlSeatError> { - self.set_selection_::( - &self.primary_selection, - selection, - TransferLocation::PrimarySelection, - ) - } - - pub fn get_primary_selection(&self) -> Option> { - self.primary_selection.get() - } - pub fn dnd_icon(&self) -> Option> { self.pointer_owner.dnd_icon() } diff --git a/src/ifs/wl_seat/focus.rs b/src/ifs/wl_seat/focus.rs new file mode 100644 index 00000000..edcb550e --- /dev/null +++ b/src/ifs/wl_seat/focus.rs @@ -0,0 +1,471 @@ +use { + super::{WlSeatGlobal, event_handling::FocusHistoryData}, + crate::{ + ifs::wl_surface::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1, + tree::{ + Direction, Node, NodeLayer, NodeLayerLink, NodeLocation, StackedNode, WorkspaceNode, + toplevel_set_workspace, + }, + utils::{ + linkedlist::{LinkedList, NodeRef}, + rc_eq::rc_weak_eq, + }, + }, + run_on_drop::on_drop, + std::{ops::Deref, rc::Rc}, +}; + +impl WlSeatGlobal { + pub fn close(self: &Rc) { + let kb_node = self.keyboard_node.get(); + if let Some(tl) = kb_node.node_toplevel() { + tl.tl_close(); + } + } + + pub fn move_focus(self: &Rc, direction: Direction) { + let tl = match self.keyboard_node.get().node_toplevel() { + Some(tl) => tl, + _ => { + if let Some(ws) = self.keyboard_node.get().node_into_workspace() + && let Some(target) = self + .state + .find_output_in_direction(&ws.output.get(), direction) + { + target.take_keyboard_navigation_focus(self, direction); + self.maybe_schedule_warp_mouse_to_focus(); + } + return; + } + }; + if direction == Direction::Down && tl.node_is_container() { + tl.node_do_focus(self, direction); + } else { + let data = tl.tl_data(); + if data.is_fullscreen.get() + && let Some(output) = data.output_opt() + && let Some(target) = self.state.find_output_in_direction(&output, direction) + { + target.take_keyboard_navigation_focus(self, direction); + } else if let Some(p) = data.parent.get() + && let Some(c) = p.node_into_container() + { + c.move_focus_from_child(self, tl.deref(), direction); + } else if let Some(float) = data.float.get() { + let ws = float.workspace.get(); + let floats: Vec<_> = ws + .stacked + .iter() + .filter_map(|node| (*node).clone().node_into_float()) + .filter(|f| f.child.get().is_some()) + .collect(); + if let Some(pos) = floats.iter().position(|f| f.id == float.id) { + let target = match direction { + Direction::Left | Direction::Down => { + if pos == 0 { + floats.last() + } else { + floats.get(pos - 1) + } + } + _ => { + if pos + 1 >= floats.len() { + floats.first() + } else { + floats.get(pos + 1) + } + } + }; + if let Some(f) = target + && f.id != float.id + { + f.clone().node_do_focus(self, Direction::Unspecified); + } + } + } + } + self.maybe_schedule_warp_mouse_to_focus(); + } + + pub fn maybe_schedule_warp_mouse_to_focus(self: &Rc) { + if self.mouse_follows_focus() { + self.warp_mouse_to_focus_skip_target_check.set(true); + self.schedule_warp_mouse_to_focus(); + } + } + + pub fn schedule_warp_mouse_to_focus(self: &Rc) { + if !self.warp_mouse_to_focus_scheduled.replace(true) { + self.state.pending_warp_mouse_to_focus.push(self.clone()); + } + } + + pub fn move_focused(self: &Rc, direction: Direction) { + let kb_node = self.keyboard_node.get(); + let Some(tl) = kb_node.node_toplevel() else { + if let Some(ws) = self.keyboard_node.get().node_into_workspace() + && let Some(target) = self + .state + .find_output_in_direction(&ws.output.get(), direction) + { + self.state.move_ws_to_output(&ws, &target); + } + return; + }; + let data = tl.tl_data(); + if data.is_fullscreen.get() + && let Some(output) = data.output_opt() + && let Some(target) = self.state.find_output_in_direction(&output, direction) + { + let ws = target.ensure_workspace(); + toplevel_set_workspace(&self.state, tl, &ws); + self.maybe_schedule_warp_mouse_to_focus(); + } else if let Some(parent) = data.parent.get() + && let Some(c) = parent.node_into_container() + { + c.move_child(tl, direction); + self.maybe_schedule_warp_mouse_to_focus(); + } else if let Some(float) = data.float.get() { + float.move_by_direction(direction); + self.maybe_schedule_warp_mouse_to_focus(); + } + } + + pub fn get_last_focus_on_workspace(&self, ws: &WorkspaceNode) -> Option> { + let mut node = self.focus_history.last()?; + loop { + if let Some(node) = node.node.upgrade() + && let Some(NodeLocation::Workspace(_, new)) = node.node_location() + && new == ws.id + { + return Some(node); + } + node = node.prev()?; + } + } + + fn get_focus_history( + &self, + next: impl Fn(&NodeRef) -> Option>, + first: impl FnOnce(&LinkedList) -> Option>, + ) -> Option<(Rc, bool)> { + let original = self.keyboard_node.get(); + let mut output = None; + let mut workspace = None; + if let Some(old) = original.node_location() { + match old { + NodeLocation::Workspace(o, w) => { + workspace = Some(w); + output = Some(o); + } + NodeLocation::Output(o) => { + output = Some(o); + } + } + } + if (output.is_none() || workspace.is_none()) + && let Some(old) = self.last_focus_location.get() + { + match old { + NodeLocation::Workspace(o, w) => { + workspace = workspace.or(Some(w)); + output = output.or(Some(o)); + } + NodeLocation::Output(o) => { + output = output.or(Some(o)); + } + } + } + if workspace.is_none() + && let Some(output) = original.node_output() + && let Some(ws) = output.workspace.get() + { + workspace = Some(ws.id); + } + let matches = |node: &FocusHistoryData| { + let visible = node.visible.get(); + if self.focus_history_visible_only.get() && !visible { + return None; + } + let node = node.node.upgrade()?; + if self.focus_history_same_workspace.get() { + let new = node.node_location()?; + let o = match new { + NodeLocation::Workspace(o, w) => { + if workspace != Some(w) { + return None; + } + o + } + NodeLocation::Output(o) => o, + }; + if output != Some(o) { + return None; + } + } + Some((node, visible)) + }; + let node = original.node_seat_state().get_focus_history(self); + if let Some(mut node) = node { + loop { + node = match next(&node) { + Some(n) => n, + _ => break, + }; + if let Some(matches) = matches(&node) { + return Some(matches); + } + } + } + let mut node = first(&self.focus_history)?; + loop { + if rc_weak_eq(&original, &node.node) { + return None; + } + if let Some(matches) = matches(&node) { + return Some(matches); + } + node = next(&node)?; + } + } + + fn focus_history( + self: &Rc, + next: impl Fn(&NodeRef) -> Option>, + first: impl FnOnce(&LinkedList) -> Option>, + ) { + let Some((node, visible)) = self.get_focus_history(next, first) else { + return; + }; + self.focus_history_rotate.fetch_add(1); + let _reset = on_drop(|| { + self.focus_history_rotate.fetch_sub(1); + }); + if !visible { + node.clone().node_make_visible(); + if !node.node_visible() { + return; + } + } + self.focus_node(node); + self.maybe_schedule_warp_mouse_to_focus(); + } + + pub fn focus_prev(self: &Rc) { + self.focus_history(|s| s.prev(), |l| l.last()); + } + + pub fn focus_next(self: &Rc) { + self.focus_history(|s| s.next(), |l| l.first()); + } + + pub fn focus_history_set_visible(&self, visible: bool) { + self.focus_history_visible_only.set(visible); + } + + pub fn focus_history_set_same_workspace(&self, same_workspace: bool) { + self.focus_history_same_workspace.set(same_workspace); + } + + fn focus_layer_rel( + self: &Rc, + next_layer: impl Fn(NodeLayer) -> NodeLayer, + layer_node_next: impl Fn( + &NodeRef>, + ) -> Option>>, + stacked_node_next: impl Fn( + &NodeRef>, + ) -> Option>>, + layer_list_iter: impl Fn(&LinkedList>) -> LI, + stacked_list_iter: impl Fn(&LinkedList>) -> SI, + ) where + LI: Iterator>>, + SI: Iterator>>, + { + fn node_viable(n: &(impl Node + ?Sized)) -> bool { + n.node_visible() && n.node_accepts_focus() + } + + let current = self.keyboard_node.get(); + let Some(output) = current.node_output() else { + return; + }; + let current_layer = current.node_layer(); + match ¤t_layer { + NodeLayerLink::Layer0(l) + | NodeLayerLink::Layer1(l) + | NodeLayerLink::Layer2(l) + | NodeLayerLink::Layer3(l) => { + if let Some(n) = layer_node_next(l) + && node_viable(&**n) + { + n.deref() + .clone() + .node_do_focus(self, Direction::Unspecified); + self.maybe_schedule_warp_mouse_to_focus(); + return; + } + } + NodeLayerLink::Stacked(l) | NodeLayerLink::StackedAboveLayers(l) => { + if let Some(n) = stacked_node_next(l) + && node_viable(&**n) + && n.node_output().map(|o| o.id) == Some(output.id) + { + n.deref() + .clone() + .node_do_focus(self, Direction::Unspecified); + self.maybe_schedule_warp_mouse_to_focus(); + return; + } + } + NodeLayerLink::Display => {} + NodeLayerLink::Output => {} + NodeLayerLink::Workspace => {} + NodeLayerLink::Tiled => {} + NodeLayerLink::Fullscreen => {} + NodeLayerLink::Lock => {} + NodeLayerLink::InputMethod => {} + } + let handle_layer_shell = |l: &LinkedList>| { + for n in layer_list_iter(l) { + if node_viable(&**n) { + return Some(n.deref().clone() as Rc); + } + } + None + }; + let handle_stacked = |l: &LinkedList>| { + for n in stacked_list_iter(l) { + if node_viable(&**n) && n.node_output().map(|o| o.id) == Some(output.id) { + return Some(n.deref().clone() as Rc); + } + } + None + }; + let ws = output.workspace.get(); + let first = next_layer(current_layer.layer()); + let mut layer = first; + loop { + let node = match layer { + NodeLayer::Display => None, + NodeLayer::Layer0 => handle_layer_shell(&output.layers[0]), + NodeLayer::Layer1 => handle_layer_shell(&output.layers[1]), + NodeLayer::Output => None, + NodeLayer::Workspace => { + if let Some(ws) = &ws + && ws.container_visible() + { + self.focus_node(ws.clone()); + self.maybe_schedule_warp_mouse_to_focus(); + return; + } + None + } + NodeLayer::Tiled => ws + .as_ref() + .and_then(|w| w.container.get()) + .map(|n| n as Rc), + NodeLayer::Fullscreen => ws + .as_ref() + .and_then(|w| w.fullscreen.get()) + .map(|n| n as Rc), + NodeLayer::Stacked => handle_stacked(&self.state.root.stacked), + NodeLayer::Layer2 => handle_layer_shell(&output.layers[2]), + NodeLayer::Layer3 => handle_layer_shell(&output.layers[3]), + NodeLayer::StackedAboveLayers => { + handle_stacked(&self.state.root.stacked_above_layers) + } + NodeLayer::Lock => None, + NodeLayer::InputMethod => None, + }; + if let Some(n) = node { + if node_viable(&*n) { + n.node_do_focus(self, Direction::Unspecified); + self.maybe_schedule_warp_mouse_to_focus(); + return; + } + } + layer = next_layer(layer); + if layer == first { + return; + } + } + } + + pub fn focus_layer_below(self: &Rc) { + self.focus_layer_rel( + |l| l.prev(), + |n| n.prev(), + |n| n.prev(), + |l| l.rev_iter(), + |l| l.rev_iter(), + ); + } + + pub fn focus_layer_above(self: &Rc) { + self.focus_layer_rel( + |l| l.next(), + |n| n.next(), + |n| n.next(), + |l| l.iter(), + |l| l.iter(), + ); + } + + pub fn toggle_focus_float_tiled(self: &Rc) { + let current = self.keyboard_node.get(); + match current.node_layer().layer() { + NodeLayer::Tiled | NodeLayer::Fullscreen => self.focus_floats(), + _ => self.focus_tiles(), + } + self.maybe_schedule_warp_mouse_to_focus(); + } + + pub fn focus_floats(self: &Rc) { + let current = self.keyboard_node.get(); + if current.node_layer().layer() == NodeLayer::Stacked { + return; + } + let Some(output) = current.node_output() else { + return; + }; + let Some(ws) = output.workspace.get() else { + return; + }; + if let Some(child) = ws + .stacked + .rev_iter() + .filter_map(|node| (*node).clone().node_into_float()) + .find_map(|float| float.child.get()) + { + child.node_do_focus(self, Direction::Unspecified); + } + } + + pub fn focus_tiles(self: &Rc) { + let current = self.keyboard_node.get(); + if matches!( + current.node_layer().layer(), + NodeLayer::Tiled | NodeLayer::Fullscreen, + ) { + return; + } + let Some(output) = current.node_output() else { + return; + }; + let Some(ws) = output.workspace.get() else { + return; + }; + let node = match ws.fullscreen.get() { + Some(fs) => fs as Rc, + _ => match ws.container.get() { + Some(c) => c, + _ => return, + }, + }; + if node.node_visible() && node.node_accepts_focus() { + node.node_do_focus(self, Direction::Unspecified); + self.maybe_schedule_warp_mouse_to_focus(); + } + } +} diff --git a/src/ifs/wl_seat/selection.rs b/src/ifs/wl_seat/selection.rs new file mode 100644 index 00000000..7509507e --- /dev/null +++ b/src/ifs/wl_seat/selection.rs @@ -0,0 +1,160 @@ +use { + super::{WlSeatError, WlSeatGlobal}, + crate::{ + client::Client, + ifs::data_transfer::{ + self, DynDataSource, TransferLocation, offer_source_to_regular_client, + wl_data_device::ClipboardTransfer, + wl_data_source::WlDataSource, + x_data_device::{XClipboardTransfer, XPrimarySelectionTransfer, XTransferDevice}, + zwp_primary_selection_device_v1::PrimarySelectionTransfer, + zwp_primary_selection_source_v1::ZwpPrimarySelectionSourceV1, + }, + utils::clonecell::CloneCell, + }, + std::rc::Rc, +}; + +impl WlSeatGlobal { + fn set_selection_( + self: &Rc, + field: &CloneCell>>, + src: Option>, + location: TransferLocation, + ) -> Result<(), WlSeatError> + where + T: data_transfer::IterableTransferVtable, + X: data_transfer::TransferVtable, + S: DynDataSource, + { + if let (Some(new), Some(old)) = (&src, &field.get()) + && new.source_data().id == old.source_data().id + { + return Ok(()); + } + if let Some(new) = &src { + data_transfer::attach_seat(&**new, self, data_transfer::Role::Selection)?; + } + let src_dyn = src.clone().map(|s| s as Rc); + if let Some(old) = field.set(src_dyn) { + old.detach_seat(self); + } + if let Some(client) = self.keyboard_node.get().node_client() { + self.offer_selection_to_client::(src.clone().map(|v| v as Rc<_>), &client); + // client.flush(); + } + let dyn_source = src.map(|s| s as Rc); + for dd in self.data_control_devices.lock().values() { + dd.clone().handle_new_source(location, dyn_source.clone()); + } + Ok(()) + } + + pub(super) fn offer_selection_to_client( + &self, + selection: Option>, + client: &Rc, + ) where + T: data_transfer::IterableTransferVtable, + X: data_transfer::TransferVtable, + { + if let Some(src) = &selection { + src.cancel_unprivileged_offers(); + } + if client.is_xwayland { + self.for_each_x_data_device(|dd| match &selection { + Some(src) => src.clone().offer_to_x(&dd), + _ => X::send_selection(&dd, None), + }); + } else { + match selection { + Some(src) => offer_source_to_regular_client::(src, client), + _ => T::for_each_device(self, client.id, |device| { + T::send_selection(device, None); + }), + } + } + } + + pub fn unset_selection(self: &Rc) { + let _ = self.set_wl_data_source_selection(None, None); + } + + pub fn set_wl_data_source_selection( + self: &Rc, + selection: Option>, + serial: Option, + ) -> Result<(), WlSeatError> { + if let Some(serial) = serial { + self.selection_serial.set(serial); + } + if let Some(selection) = &selection + && selection.toplevel_drag.is_some() + { + return Err(WlSeatError::OfferHasDrag); + } + self.set_selection(selection) + } + + pub fn set_selection( + self: &Rc, + selection: Option>, + ) -> Result<(), WlSeatError> { + self.set_selection_::( + &self.selection, + selection, + TransferLocation::Clipboard, + ) + } + + pub fn get_selection(&self) -> Option> { + self.selection.get() + } + + pub fn may_modify_selection(&self, client: &Rc, serial: u64) -> bool { + if serial < self.selection_serial.get() { + return false; + } + self.keyboard_node.get().node_client_id() == Some(client.id) + } + + pub fn may_modify_primary_selection(&self, client: &Rc, serial: Option) -> bool { + if let Some(serial) = serial + && serial < self.primary_selection_serial.get() + { + return false; + } + self.keyboard_node.get().node_client_id() == Some(client.id) + || self.pointer_node().and_then(|n| n.node_client_id()) == Some(client.id) + } + + pub fn unset_primary_selection(self: &Rc) { + let _ = self.set_zwp_primary_selection(None, None); + } + + pub fn set_zwp_primary_selection( + self: &Rc, + selection: Option>, + serial: Option, + ) -> Result<(), WlSeatError> { + if let Some(serial) = serial { + self.primary_selection_serial.set(serial); + } + self.set_primary_selection(selection) + } + + pub fn set_primary_selection( + self: &Rc, + selection: Option>, + ) -> Result<(), WlSeatError> { + self.set_selection_::( + &self.primary_selection, + selection, + TransferLocation::PrimarySelection, + ) + } + + pub fn get_primary_selection(&self) -> Option> { + self.primary_selection.get() + } +} diff --git a/src/ifs/wl_seat/window_management.rs b/src/ifs/wl_seat/window_management.rs new file mode 100644 index 00000000..0163a15a --- /dev/null +++ b/src/ifs/wl_seat/window_management.rs @@ -0,0 +1,108 @@ +use { + super::WlSeatGlobal, + crate::tree::{ + ChangeGroupAction, ContainerNode, ContainerSplit, toplevel_create_split, + toplevel_parent_container, toplevel_set_floating, + }, + std::{ops::Deref, rc::Rc}, +}; + +impl WlSeatGlobal { + pub fn kb_parent_container(&self) -> Option> { + if let Some(tl) = self.keyboard_node.get().node_toplevel() { + return toplevel_parent_container(&*tl); + } + None + } + + pub fn get_mono(&self) -> Option { + self.kb_parent_container().map(|c| c.mono_child.is_some()) + } + + pub fn get_split(&self) -> Option { + self.kb_parent_container().map(|c| c.split.get()) + } + + pub fn set_mono(&self, mono: bool) { + if let Some(tl) = self.keyboard_node.get().node_toplevel() + && let Some(parent) = tl.tl_data().parent.get() + && let Some(container) = parent.node_into_container() + { + let node = if mono { Some(tl.deref()) } else { None }; + container.set_mono(node); + } + } + + pub fn set_split(&self, axis: ContainerSplit) { + if let Some(c) = self.kb_parent_container() { + c.set_split(axis); + } + } + + pub fn create_split(&self, axis: ContainerSplit) { + let tl = match self.keyboard_node.get().node_toplevel() { + Some(tl) => tl, + _ => return, + }; + toplevel_create_split(&self.state, tl, axis); + } + + pub fn toggle_tab(&self) { + if let Some(c) = self.kb_parent_container() { + c.change_group(ChangeGroupAction::ToggleTab); + } + } + + pub fn make_group(&self, axis: ContainerSplit, ephemeral: bool) { + if let Some(c) = self.kb_parent_container() { + c.make_group(axis, ephemeral); + } + } + + pub fn change_group_opposite(&self) { + if let Some(c) = self.kb_parent_container() { + c.change_group(ChangeGroupAction::Opposite); + } + } + + pub fn equalize(&self, recursive: bool) { + if let Some(c) = self.kb_parent_container() { + if recursive { + c.equalize_recursive(); + } else { + c.equalize(); + } + } + } + + pub fn move_tab(&self, right: bool) { + if let Some(c) = self.kb_parent_container() { + c.move_tab(right); + } + } + + pub fn focus_parent(self: &Rc) { + if let Some(tl) = self.keyboard_node.get().node_toplevel() + && let Some(parent) = tl.tl_data().parent.get() + && let Some(tl) = parent.node_toplevel() + { + self.focus_node(tl); + self.maybe_schedule_warp_mouse_to_focus(); + } + } + + pub fn get_floating(self: &Rc) -> Option { + match self.keyboard_node.get().node_toplevel() { + Some(tl) => Some(tl.tl_data().parent_is_float.get()), + _ => None, + } + } + + pub fn set_floating(self: &Rc, floating: bool) { + let tl = match self.keyboard_node.get().node_toplevel() { + Some(tl) => tl, + _ => return, + }; + toplevel_set_floating(&self.state, tl, floating); + } +} diff --git a/src/tree/container.rs b/src/tree/container.rs index 3b981490..3fbda5a7 100644 --- a/src/tree/container.rs +++ b/src/tree/container.rs @@ -1,4 +1,5 @@ mod drag_destination; +mod layout; mod tasks; pub use drag_destination::default_tile_drag_destination; @@ -45,7 +46,7 @@ use { cell::{Cell, RefCell}, fmt::{Debug, Formatter}, mem, - ops::{Deref, DerefMut, Sub}, + ops::{Deref, DerefMut}, rc::Rc, }, }; @@ -190,23 +191,6 @@ struct CursorState { op: Option, } -impl ContainerChild { - fn position_content(&self) { - let mut content = self.content.get(); - let body = self.body.get(); - let width = content.width(); - let height = content.height(); - // let x1 = body.x1() + (body.width() - width) / 2; - // let y1 = body.y1() + (body.height() - height) / 2; - let x1 = body.x1(); - let y1 = body.y1(); - content = Rect::new_sized_saturating(x1, y1, width, height); - // log::debug!("body: {:?}", body); - // log::debug!("content: {:?}", content); - self.content.set(content); - } -} - impl ContainerNode { pub fn new( state: &Rc, @@ -391,218 +375,6 @@ impl ContainerNode { } } - pub fn predict_child_body_size(&self) -> (i32, i32) { - if self.mono_child.is_some() { - let mb = self.mono_body.get(); - return (mb.width(), mb.height()); - } - let nc = self.num_children.get() as i32 + 1; - match self.split.get() { - ContainerSplit::Horizontal => { - let spacing = self.child_spacing(); - let content_w = self.width.get().sub((nc - 1) * spacing).max(0); - (content_w / nc, self.height.get()) - } - ContainerSplit::Vertical => { - let spacing = self.child_spacing(); - let content_h = self.height.get().sub((nc - 1) * spacing).max(0); - (self.width.get(), content_h / nc) - } - } - } - - pub fn on_spaces_changed(self: &Rc) { - self.update_content_size(); - // log::info!("on_spaces_changed"); - self.schedule_layout(); - self.schedule_compute_render_positions(); - } - - pub fn on_colors_changed(self: &Rc) { - self.schedule_compute_render_positions(); - } - - fn damage(&self) { - let bw = if self.state.theme.sizes.gap.get() != 0 { - self.state.theme.sizes.border_width.get() - } else { - 0 - }; - self.state.damage(Rect::new_sized_saturating( - self.abs_x1.get() - bw, - self.abs_y1.get() - bw, - self.width.get() + 2 * bw, - self.height.get() + 2 * bw, - )); - } - - fn child_spacing(&self) -> i32 { - let gap = self.state.theme.sizes.gap.get(); - let bw = self.state.theme.sizes.border_width.get(); - if gap == 0 { bw } else { gap + 2 * bw } - } - - fn schedule_layout(self: &Rc) { - if self.state.layout_animations_requested.get() || self.state.layout_animations_active.get() - { - self.animate_next_layout.set(true); - } - if !self.layout_scheduled.replace(true) { - self.state.pending_container_layout.push(self.clone()); - } - } - - fn schedule_layout_immediate(self: &Rc) { - self.schedule_layout(); - if self.toplevel_data.visible.get() { - self.damage(); - } - } - - fn all_children_match_body(&self) -> bool { - if let Some(mono) = self.mono_child.get() { - let body = self.mono_body.get(); - let content = mono.content.get(); - return content.width() == body.width() && content.height() == body.height(); - } - for child in self.children.iter() { - let body = child.body.get(); - let content = child.content.get(); - if content.width() != body.width() || content.height() != body.height() { - return false; - } - } - true - } - - fn perform_layout(self: &Rc) { - self.layout_scheduled.set(false); - if self.num_children.get() == 0 { - self.mono_transition_animation_pending.set(false); - return; - } - if let Some(child) = self.mono_child.get() { - self.perform_mono_layout(&child); - } else { - self.perform_split_layout(); - } - self.state.tree_changed(); - // log::info!("perform_layout"); - self.schedule_compute_render_positions(); - self.layout_complete.trigger(); - if self.all_children_match_body() { - self.all_children_resized.trigger(); - if self.toplevel_data.visible.get() { - self.damage(); - } - } - self.mono_transition_animation_pending.set(false); - } - - fn perform_mono_layout(self: &Rc, child: &ContainerChild) { - let mb = self.mono_body.get(); - child - .node - .clone() - .tl_change_extents(&mb.move_(self.abs_x1.get(), self.abs_y1.get())); - self.mono_content - .set(child.content.get().at_point(mb.x1(), mb.y1())); - } - - fn perform_split_layout(self: &Rc) { - let sum_factors = self.sum_factors.get(); - let split = self.split.get(); - let spacing = self.child_spacing(); - let (content_size, other_content_size) = match split { - ContainerSplit::Horizontal => (self.content_width.get(), self.content_height.get()), - ContainerSplit::Vertical => (self.content_height.get(), self.content_width.get()), - }; - let num_children = self.num_children.get(); - if num_children == 0 { - return; - } - let mut pos = 0; - let mut remaining_content_size = content_size; - for child in self.children.iter() { - let factor = child.factor.get() / sum_factors; - child.factor.set(factor); - let mut body_size = (content_size as f64 * factor).round() as i32; - body_size = body_size.min(remaining_content_size); - remaining_content_size -= body_size; - let (x1, y1, width, height) = match split { - ContainerSplit::Horizontal => (pos, 0, body_size, other_content_size), - _ => (0, pos, other_content_size, body_size), - }; - let body = Rect::new_sized_saturating(x1, y1, width, height); - child.body.set(body); - pos += body_size + spacing; - } - if remaining_content_size > 0 { - let size_per = remaining_content_size / num_children as i32; - let mut rem = remaining_content_size % num_children as i32; - pos = 0; - for child in self.children.iter() { - let mut body = child.body.get(); - let mut add = size_per; - if rem > 0 { - rem -= 1; - add += 1; - } - let (x1, y1, width, height, size) = match split { - ContainerSplit::Horizontal => { - let width = body.width() + add; - (pos, 0, width, other_content_size, width) - } - _ => { - let height = body.height() + add; - (0, pos, other_content_size, height, height) - } - }; - body = Rect::new_sized_saturating(x1, y1, width, height); - child.body.set(body); - pos += size + spacing; - } - } - self.sum_factors.set(1.0); - for child in self.children.iter() { - let body = child.body.get(); - let body = body.move_(self.abs_x1.get(), self.abs_y1.get()); - child.node.clone().tl_change_extents(&body); - child.position_content(); - } - } - - fn update_content_size(&self) { - let nc = self.num_children.get(); - let spacing = self.child_spacing(); - match self.split.get() { - ContainerSplit::Horizontal => { - let new_content_size = self.width.get().sub((nc - 1) as i32 * spacing).max(0); - self.content_width.set(new_content_size); - self.content_height.set(self.height.get()); - } - ContainerSplit::Vertical => { - let new_content_size = self.height.get().sub((nc - 1) as i32 * spacing).max(0); - self.content_height.set(new_content_size); - self.content_width.set(self.width.get()); - } - } - let tab_bar_height = if self.mono_child.is_some() { - // Tab bar sits above the window with a configurable gap. - let tbh = self.state.theme.sizes.tab_bar_height.get(); - let gap = self.state.theme.sizes.tab_bar_gap.get(); - tbh + gap - } else { - 0 - }; - self.mono_body.set(Rect::new_sized_saturating( - 0, - tab_bar_height, - self.width.get(), - (self.height.get() - tab_bar_height).max(0), - )); - } - fn pointer_move( self: &Rc, _seat: &Rc, diff --git a/src/tree/container/layout.rs b/src/tree/container/layout.rs new file mode 100644 index 00000000..e04f2366 --- /dev/null +++ b/src/tree/container/layout.rs @@ -0,0 +1,236 @@ +use { + super::{ContainerChild, ContainerNode, ContainerSplit}, + crate::rect::Rect, + std::{ops::Sub, rc::Rc}, +}; + +impl ContainerChild { + pub(super) fn position_content(&self) { + let mut content = self.content.get(); + let body = self.body.get(); + let width = content.width(); + let height = content.height(); + // let x1 = body.x1() + (body.width() - width) / 2; + // let y1 = body.y1() + (body.height() - height) / 2; + let x1 = body.x1(); + let y1 = body.y1(); + content = Rect::new_sized_saturating(x1, y1, width, height); + // log::debug!("body: {:?}", body); + // log::debug!("content: {:?}", content); + self.content.set(content); + } +} + +impl ContainerNode { + pub fn predict_child_body_size(&self) -> (i32, i32) { + if self.mono_child.is_some() { + let mb = self.mono_body.get(); + return (mb.width(), mb.height()); + } + let nc = self.num_children.get() as i32 + 1; + match self.split.get() { + ContainerSplit::Horizontal => { + let spacing = self.child_spacing(); + let content_w = self.width.get().sub((nc - 1) * spacing).max(0); + (content_w / nc, self.height.get()) + } + ContainerSplit::Vertical => { + let spacing = self.child_spacing(); + let content_h = self.height.get().sub((nc - 1) * spacing).max(0); + (self.width.get(), content_h / nc) + } + } + } + + pub fn on_spaces_changed(self: &Rc) { + self.update_content_size(); + // log::info!("on_spaces_changed"); + self.schedule_layout(); + self.schedule_compute_render_positions(); + } + + pub fn on_colors_changed(self: &Rc) { + self.schedule_compute_render_positions(); + } + + pub(super) fn damage(&self) { + let bw = if self.state.theme.sizes.gap.get() != 0 { + self.state.theme.sizes.border_width.get() + } else { + 0 + }; + self.state.damage(Rect::new_sized_saturating( + self.abs_x1.get() - bw, + self.abs_y1.get() - bw, + self.width.get() + 2 * bw, + self.height.get() + 2 * bw, + )); + } + + pub(super) fn child_spacing(&self) -> i32 { + let gap = self.state.theme.sizes.gap.get(); + let bw = self.state.theme.sizes.border_width.get(); + if gap == 0 { bw } else { gap + 2 * bw } + } + + pub(super) fn schedule_layout(self: &Rc) { + if self.state.layout_animations_requested.get() || self.state.layout_animations_active.get() + { + self.animate_next_layout.set(true); + } + if !self.layout_scheduled.replace(true) { + self.state.pending_container_layout.push(self.clone()); + } + } + + pub(super) fn schedule_layout_immediate(self: &Rc) { + self.schedule_layout(); + if self.toplevel_data.visible.get() { + self.damage(); + } + } + + pub(super) fn all_children_match_body(&self) -> bool { + if let Some(mono) = self.mono_child.get() { + let body = self.mono_body.get(); + let content = mono.content.get(); + return content.width() == body.width() && content.height() == body.height(); + } + for child in self.children.iter() { + let body = child.body.get(); + let content = child.content.get(); + if content.width() != body.width() || content.height() != body.height() { + return false; + } + } + true + } + + pub(super) fn perform_layout(self: &Rc) { + self.layout_scheduled.set(false); + if self.num_children.get() == 0 { + self.mono_transition_animation_pending.set(false); + return; + } + if let Some(child) = self.mono_child.get() { + self.perform_mono_layout(&child); + } else { + self.perform_split_layout(); + } + self.state.tree_changed(); + // log::info!("perform_layout"); + self.schedule_compute_render_positions(); + self.layout_complete.trigger(); + if self.all_children_match_body() { + self.all_children_resized.trigger(); + if self.toplevel_data.visible.get() { + self.damage(); + } + } + self.mono_transition_animation_pending.set(false); + } + + fn perform_mono_layout(self: &Rc, child: &ContainerChild) { + let mb = self.mono_body.get(); + child + .node + .clone() + .tl_change_extents(&mb.move_(self.abs_x1.get(), self.abs_y1.get())); + self.mono_content + .set(child.content.get().at_point(mb.x1(), mb.y1())); + } + + fn perform_split_layout(self: &Rc) { + let sum_factors = self.sum_factors.get(); + let split = self.split.get(); + let spacing = self.child_spacing(); + let (content_size, other_content_size) = match split { + ContainerSplit::Horizontal => (self.content_width.get(), self.content_height.get()), + ContainerSplit::Vertical => (self.content_height.get(), self.content_width.get()), + }; + let num_children = self.num_children.get(); + if num_children == 0 { + return; + } + let mut pos = 0; + let mut remaining_content_size = content_size; + for child in self.children.iter() { + let factor = child.factor.get() / sum_factors; + child.factor.set(factor); + let mut body_size = (content_size as f64 * factor).round() as i32; + body_size = body_size.min(remaining_content_size); + remaining_content_size -= body_size; + let (x1, y1, width, height) = match split { + ContainerSplit::Horizontal => (pos, 0, body_size, other_content_size), + _ => (0, pos, other_content_size, body_size), + }; + let body = Rect::new_sized_saturating(x1, y1, width, height); + child.body.set(body); + pos += body_size + spacing; + } + if remaining_content_size > 0 { + let size_per = remaining_content_size / num_children as i32; + let mut rem = remaining_content_size % num_children as i32; + pos = 0; + for child in self.children.iter() { + let mut body = child.body.get(); + let mut add = size_per; + if rem > 0 { + rem -= 1; + add += 1; + } + let (x1, y1, width, height, size) = match split { + ContainerSplit::Horizontal => { + let width = body.width() + add; + (pos, 0, width, other_content_size, width) + } + _ => { + let height = body.height() + add; + (0, pos, other_content_size, height, height) + } + }; + body = Rect::new_sized_saturating(x1, y1, width, height); + child.body.set(body); + pos += size + spacing; + } + } + self.sum_factors.set(1.0); + for child in self.children.iter() { + let body = child.body.get(); + let body = body.move_(self.abs_x1.get(), self.abs_y1.get()); + child.node.clone().tl_change_extents(&body); + child.position_content(); + } + } + + pub(super) fn update_content_size(&self) { + let nc = self.num_children.get(); + let spacing = self.child_spacing(); + match self.split.get() { + ContainerSplit::Horizontal => { + let new_content_size = self.width.get().sub((nc - 1) as i32 * spacing).max(0); + self.content_width.set(new_content_size); + self.content_height.set(self.height.get()); + } + ContainerSplit::Vertical => { + let new_content_size = self.height.get().sub((nc - 1) as i32 * spacing).max(0); + self.content_height.set(new_content_size); + self.content_width.set(self.width.get()); + } + } + let tab_bar_height = if self.mono_child.is_some() { + // Tab bar sits above the window with a configurable gap. + let tbh = self.state.theme.sizes.tab_bar_height.get(); + let gap = self.state.theme.sizes.tab_bar_gap.get(); + tbh + gap + } else { + 0 + }; + self.mono_body.set(Rect::new_sized_saturating( + 0, + tab_bar_height, + self.width.get(), + (self.height.get() - tab_bar_height).max(0), + )); + } +} diff --git a/src/tree/output.rs b/src/tree/output.rs index b06b951e..bf12bc48 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -1,5 +1,7 @@ +mod captures; mod policy; mod render_data; +mod workspaces; #[allow(unused_imports)] pub use { @@ -17,17 +19,14 @@ use { HardwareCursor, Mode, transaction::BackendConnectorTransactionError, }, client::ClientId, - cmm::cmm_description::ColorDescription, cursor::KnownCursor, fixed::Fixed, - gfx_api::{AcquireSync, BufferResv, GfxTexture, ReleaseSync}, ifs::{ ext_image_copy::ext_image_copy_capture_session_v1::ExtImageCopyCaptureSessionV1, jay_output::JayOutput, - wl_buffer::WlBufferStorage, wl_output::{BlendSpace, WlOutputGlobal}, wl_seat::{ - BTN_LEFT, NodeSeatState, SeatId, WlSeatGlobal, collect_kb_foci2, + BTN_LEFT, NodeSeatState, SeatId, WlSeatGlobal, tablet::{TabletTool, TabletToolChanges, TabletToolId}, wl_pointer::PendingScroll, }, @@ -68,8 +67,7 @@ use { copyhashmap::CopyHashMap, errorfmt::ErrorFmt, event_listener::{EventSource, LazyEventSource}, - hash_map_ext::HashMapExt, - linkedlist::{LinkedList, NodeRef}, + linkedlist::LinkedList, on_drop_event::OnDropEvent, scroller::Scroller, }, @@ -77,9 +75,7 @@ use { ExtImageCopyCaptureSessionV1Id, JayOutputId, ZwlrScreencopyFrameV1Id, }, }, - ahash::AHashMap, numeric_sort::cmp, - smallvec::SmallVec, std::{ cell::{Cell, RefCell}, fmt::{Debug, Formatter}, @@ -280,162 +276,6 @@ impl OutputNode { } } - pub fn captures_changed(&self) { - for ws in self.workspaces.iter() { - ws.update_has_captures(); - } - } - - pub fn perform_screencopies( - &self, - tex: &Rc, - cd: &Rc, - resv: Option<&Rc>, - acquire_sync: &AcquireSync, - release_sync: ReleaseSync, - render_hardware_cursor: bool, - x_off: i32, - y_off: i32, - size: Option<(i32, i32)>, - ) { - if let Some(workspace) = self.workspace.get() { - if !workspace.may_capture.get() { - return; - } - } - self.perform_wlr_screencopies( - tex, - cd, - resv, - acquire_sync, - release_sync, - render_hardware_cursor, - x_off, - y_off, - size, - ); - for sc in self.ext_copy_sessions.lock().values() { - sc.copy_texture( - self, - tex, - cd, - resv, - acquire_sync, - release_sync, - render_hardware_cursor, - x_off, - y_off, - size, - ); - } - } - - pub fn perform_wlr_screencopies( - &self, - tex: &Rc, - cd: &Rc, - resv: Option<&Rc>, - acquire_sync: &AcquireSync, - release_sync: ReleaseSync, - render_hardware_cursors: bool, - x_off: i32, - y_off: i32, - size: Option<(i32, i32)>, - ) { - if self.screencopies.is_empty() { - return; - } - let now = self.state.now(); - for capture in self.screencopies.lock().drain_values() { - let wl_buffer = match capture.buffer.take() { - Some(b) => b, - _ => { - log::warn!("Capture frame is pending but has no buffer attached"); - capture.send_failed(); - continue; - } - }; - if wl_buffer.destroyed() { - capture.send_failed(); - continue; - } - let mut ready = true; - if let Some(storage) = wl_buffer.storage.borrow_mut().deref() { - match storage { - WlBufferStorage::Shm { mem, stride, .. } => { - let res = self.state.perform_shm_screencopy( - tex, - cd, - acquire_sync, - self.global.pos.get(), - x_off, - y_off, - size, - &capture, - mem, - *stride, - wl_buffer.format, - self.global.persistent.transform.get(), - self.global.persistent.scale.get(), - ); - match res { - Ok(p) => { - ready = p.is_none(); - capture.pending.set(p); - } - Err(e) => { - log::warn!("Could not perform shm screencopy: {}", ErrorFmt(e)); - capture.send_failed(); - continue; - } - } - } - WlBufferStorage::Dmabuf { fb, .. } => { - let fb = match fb { - Some(fb) => fb, - _ => { - log::warn!("Capture buffer has no framebuffer"); - capture.send_failed(); - continue; - } - }; - let res = self.state.perform_screencopy( - tex, - resv, - acquire_sync, - release_sync, - cd, - &fb, - AcquireSync::Implicit, - ReleaseSync::Implicit, - self.global.persistent.transform.get(), - self.state.color_manager.srgb_gamma22(), - self.global.pos.get(), - render_hardware_cursors, - x_off - capture.rect.x1(), - y_off - capture.rect.y1(), - size, - self.global.persistent.transform.get(), - self.global.persistent.scale.get(), - ); - if let Err(e) = res { - log::warn!("Could not perform screencopy: {}", ErrorFmt(e)); - capture.send_failed(); - continue; - } - } - } - } - if capture.with_damage.get() { - capture.send_damage(); - } - if ready { - capture.send_ready(now.0.tv_sec as _, now.0.tv_nsec as _); - } - } - self.captures_changed(); - } - pub fn clear(&self) { self.global.clear(); self.workspace.set(None); @@ -648,117 +488,6 @@ impl OutputNode { } } - pub fn ensure_workspace(self: &Rc) -> Rc { - if let Some(ws) = self.workspace.get() { - if !ws.is_dummy { - return ws; - } - } - self.generate_workspace() - } - - pub fn generate_workspace(self: &Rc) -> Rc { - let name = 'name: { - for i in 1.. { - let name = i.to_string(); - if self.find_workspace(&name).is_none() { - break 'name name; - } - } - unreachable!(); - }; - self.create_workspace(&name) - } - - pub fn find_workspace(&self, name: &str) -> Option> { - self.workspaces - .iter() - .find(|ws| ws.name.as_str() == name) - .map(|ws| (*ws).clone()) - } - - pub fn show_workspace(&self, ws: &Rc) -> bool { - let mut seats = SmallVec::new(); - if let Some(old) = self.workspace.set(Some(ws.clone())) { - if old.id == ws.id { - return false; - } - collect_kb_foci2(old.clone(), &mut seats); - for pinned in self.pinned.iter() { - pinned.deref().clone().set_workspace(ws, false); - } - if old.is_empty() { - for jw in old.jay_workspaces.lock().values() { - jw.send_destroyed(); - jw.workspace.set(None); - } - for wh in old.ext_workspaces.lock().values() { - wh.handle_destroyed(); - } - old.clear(); - self.state.workspaces.remove(&old.id); - } else { - old.set_visible(false); - old.flush_jay_workspaces(); - } - } - self.update_visible(); - self.update_presentation_type(); - if let Some(fs) = ws.fullscreen.get() { - fs.tl_change_extents(&self.global.pos.get()); - } - ws.change_extents(&self.workspace_rect.get()); - for seat in seats { - ws.clone().node_do_focus(&seat, Direction::Unspecified); - } - if self.node_visible() { - self.state.damage(self.global.pos.get()); - } - true - } - - pub fn find_workspace_insertion_point(&self, name: &str) -> Option>> { - if self.state.workspace_display_order.get() == WorkspaceDisplayOrder::Sorted { - for existing_ws in self.workspaces.iter() { - if cmp(name, &existing_ws.name) == std::cmp::Ordering::Less { - return Some(existing_ws); - } - } - } - None - } - - pub fn create_workspace(self: &Rc, name: &str) -> Rc { - let ws = WorkspaceNode::new(self, name, false); - ws.opt.set(Some(ws.clone())); - ws.update_has_captures(); - let link = if let Some(before) = self.find_workspace_insertion_point(name) { - before.prepend(ws.clone()) - } else { - self.workspaces.add_last(ws.clone()) - }; - *ws.output_link.borrow_mut() = Some(link); - self.state.workspaces.set(ws.id, ws.clone()); - if self.workspace.is_none() { - self.show_workspace(&ws); - } - let mut clients_to_kill = AHashMap::new(); - for watcher in self.state.workspace_watchers.lock().values() { - if let Err(e) = watcher.send_workspace(&ws) { - clients_to_kill.insert(watcher.client.id, (watcher.client.clone(), e)); - } - } - for (client, e) in clients_to_kill.values() { - client.error(e); - } - self.state.workspace_managers.announce_workspace(self, &ws); - self.state - .workspace_managers - .update_workspace_coordinates(self); - self.schedule_update_render_data(); - ws - } - pub fn update_rects(self: &Rc) { let rect = self.global.pos.get(); let bh = self.state.theme.sizes.bar_height(); diff --git a/src/tree/output/captures.rs b/src/tree/output/captures.rs new file mode 100644 index 00000000..2fa93119 --- /dev/null +++ b/src/tree/output/captures.rs @@ -0,0 +1,168 @@ +use { + super::OutputNode, + crate::{ + cmm::cmm_description::ColorDescription, + gfx_api::{AcquireSync, BufferResv, GfxTexture, ReleaseSync}, + ifs::wl_buffer::WlBufferStorage, + utils::{errorfmt::ErrorFmt, hash_map_ext::HashMapExt}, + }, + std::{ops::Deref, rc::Rc}, +}; + +impl OutputNode { + pub fn captures_changed(&self) { + for ws in self.workspaces.iter() { + ws.update_has_captures(); + } + } + + pub fn perform_screencopies( + &self, + tex: &Rc, + cd: &Rc, + resv: Option<&Rc>, + acquire_sync: &AcquireSync, + release_sync: ReleaseSync, + render_hardware_cursor: bool, + x_off: i32, + y_off: i32, + size: Option<(i32, i32)>, + ) { + if let Some(workspace) = self.workspace.get() { + if !workspace.may_capture.get() { + return; + } + } + self.perform_wlr_screencopies( + tex, + cd, + resv, + acquire_sync, + release_sync, + render_hardware_cursor, + x_off, + y_off, + size, + ); + for sc in self.ext_copy_sessions.lock().values() { + sc.copy_texture( + self, + tex, + cd, + resv, + acquire_sync, + release_sync, + render_hardware_cursor, + x_off, + y_off, + size, + ); + } + } + + pub fn perform_wlr_screencopies( + &self, + tex: &Rc, + cd: &Rc, + resv: Option<&Rc>, + acquire_sync: &AcquireSync, + release_sync: ReleaseSync, + render_hardware_cursors: bool, + x_off: i32, + y_off: i32, + size: Option<(i32, i32)>, + ) { + if self.screencopies.is_empty() { + return; + } + let now = self.state.now(); + for capture in self.screencopies.lock().drain_values() { + let wl_buffer = match capture.buffer.take() { + Some(b) => b, + _ => { + log::warn!("Capture frame is pending but has no buffer attached"); + capture.send_failed(); + continue; + } + }; + if wl_buffer.destroyed() { + capture.send_failed(); + continue; + } + let mut ready = true; + if let Some(storage) = wl_buffer.storage.borrow_mut().deref() { + match storage { + WlBufferStorage::Shm { mem, stride, .. } => { + let res = self.state.perform_shm_screencopy( + tex, + cd, + acquire_sync, + self.global.pos.get(), + x_off, + y_off, + size, + &capture, + mem, + *stride, + wl_buffer.format, + self.global.persistent.transform.get(), + self.global.persistent.scale.get(), + ); + match res { + Ok(p) => { + ready = p.is_none(); + capture.pending.set(p); + } + Err(e) => { + log::warn!("Could not perform shm screencopy: {}", ErrorFmt(e)); + capture.send_failed(); + continue; + } + } + } + WlBufferStorage::Dmabuf { fb, .. } => { + let fb = match fb { + Some(fb) => fb, + _ => { + log::warn!("Capture buffer has no framebuffer"); + capture.send_failed(); + continue; + } + }; + let res = self.state.perform_screencopy( + tex, + resv, + acquire_sync, + release_sync, + cd, + &fb, + AcquireSync::Implicit, + ReleaseSync::Implicit, + self.global.persistent.transform.get(), + self.state.color_manager.srgb_gamma22(), + self.global.pos.get(), + render_hardware_cursors, + x_off - capture.rect.x1(), + y_off - capture.rect.y1(), + size, + self.global.persistent.transform.get(), + self.global.persistent.scale.get(), + ); + if let Err(e) = res { + log::warn!("Could not perform screencopy: {}", ErrorFmt(e)); + capture.send_failed(); + continue; + } + } + } + } + if capture.with_damage.get() { + capture.send_damage(); + } + if ready { + capture.send_ready(now.0.tv_sec as _, now.0.tv_nsec as _); + } + } + self.captures_changed(); + } +} diff --git a/src/tree/output/workspaces.rs b/src/tree/output/workspaces.rs new file mode 100644 index 00000000..5b83fde5 --- /dev/null +++ b/src/tree/output/workspaces.rs @@ -0,0 +1,125 @@ +use { + super::OutputNode, + crate::{ + ifs::wl_seat::collect_kb_foci2, + tree::{Direction, Node, WorkspaceDisplayOrder, WorkspaceNode}, + utils::linkedlist::NodeRef, + }, + ahash::AHashMap, + numeric_sort::cmp, + smallvec::SmallVec, + std::{ops::Deref, rc::Rc}, +}; + +impl OutputNode { + pub fn ensure_workspace(self: &Rc) -> Rc { + if let Some(ws) = self.workspace.get() { + if !ws.is_dummy { + return ws; + } + } + self.generate_workspace() + } + + pub fn generate_workspace(self: &Rc) -> Rc { + let name = 'name: { + for i in 1.. { + let name = i.to_string(); + if self.find_workspace(&name).is_none() { + break 'name name; + } + } + unreachable!(); + }; + self.create_workspace(&name) + } + + pub fn find_workspace(&self, name: &str) -> Option> { + self.workspaces + .iter() + .find(|ws| ws.name.as_str() == name) + .map(|ws| (*ws).clone()) + } + + pub fn show_workspace(&self, ws: &Rc) -> bool { + let mut seats = SmallVec::new(); + if let Some(old) = self.workspace.set(Some(ws.clone())) { + if old.id == ws.id { + return false; + } + collect_kb_foci2(old.clone(), &mut seats); + for pinned in self.pinned.iter() { + pinned.deref().clone().set_workspace(ws, false); + } + if old.is_empty() { + for jw in old.jay_workspaces.lock().values() { + jw.send_destroyed(); + jw.workspace.set(None); + } + for wh in old.ext_workspaces.lock().values() { + wh.handle_destroyed(); + } + old.clear(); + self.state.workspaces.remove(&old.id); + } else { + old.set_visible(false); + old.flush_jay_workspaces(); + } + } + self.update_visible(); + self.update_presentation_type(); + if let Some(fs) = ws.fullscreen.get() { + fs.tl_change_extents(&self.global.pos.get()); + } + ws.change_extents(&self.workspace_rect.get()); + for seat in seats { + ws.clone().node_do_focus(&seat, Direction::Unspecified); + } + if self.node_visible() { + self.state.damage(self.global.pos.get()); + } + true + } + + pub fn find_workspace_insertion_point(&self, name: &str) -> Option>> { + if self.state.workspace_display_order.get() == WorkspaceDisplayOrder::Sorted { + for existing_ws in self.workspaces.iter() { + if cmp(name, &existing_ws.name) == std::cmp::Ordering::Less { + return Some(existing_ws); + } + } + } + None + } + + pub fn create_workspace(self: &Rc, name: &str) -> Rc { + let ws = WorkspaceNode::new(self, name, false); + ws.opt.set(Some(ws.clone())); + ws.update_has_captures(); + let link = if let Some(before) = self.find_workspace_insertion_point(name) { + before.prepend(ws.clone()) + } else { + self.workspaces.add_last(ws.clone()) + }; + *ws.output_link.borrow_mut() = Some(link); + self.state.workspaces.set(ws.id, ws.clone()); + if self.workspace.is_none() { + self.show_workspace(&ws); + } + let mut clients_to_kill = AHashMap::new(); + for watcher in self.state.workspace_watchers.lock().values() { + if let Err(e) = watcher.send_workspace(&ws) { + clients_to_kill.insert(watcher.client.id, (watcher.client.clone(), e)); + } + } + for (client, e) in clients_to_kill.values() { + client.error(e); + } + self.state.workspace_managers.announce_workspace(self, &ws); + self.state + .workspace_managers + .update_workspace_coordinates(self); + self.schedule_update_render_data(); + ws + } +} diff --git a/src/xwayland/xwm.rs b/src/xwayland/xwm.rs index d35b4d2a..cfd31f4b 100644 --- a/src/xwayland/xwm.rs +++ b/src/xwayland/xwm.rs @@ -1,6 +1,7 @@ #![allow(clippy::await_holding_refcell_ref)] // all borrows are to data that is only used by this task mod selection; +mod properties; mod transfer; use selection::SelectionData; @@ -9,7 +10,6 @@ use { crate::{ async_engine::SpawnedFuture, client::Client, - criteria::tlm::{TL_CHANGED_CLASS_INST, TL_CHANGED_ROLE}, ifs::{ data_transfer::{ DataOfferId, DataSourceId, DynDataOffer, DynDataSource, TransferLocation, TransferVtable, @@ -22,7 +22,7 @@ use { wl_seat::{SeatId, WlSeatGlobal}, wl_surface::{ WlSurface, - x_surface::xwindow::{XInputModel, Xwindow, XwindowData}, + x_surface::xwindow::{Xwindow, XwindowData}, }, }, rect::Rect, @@ -46,7 +46,7 @@ use { ChangeProperty, ChangeWindowAttributes, ClientMessage, CompositeRedirectSubwindows, ConfigureNotify, ConfigureRequest, ConfigureWindow, ConfigureWindowValues, ConvertSelection, CreateNotify, CreateWindow, CreateWindowValues, DestroyNotify, - Extension, FocusIn, GetAtomName, GetGeometry, InternAtom, KillClient, MapNotify, + Extension, FocusIn, GetGeometry, InternAtom, KillClient, MapNotify, MapRequest, MapWindow, PropertyNotify, ResClientIdSpec, ResQueryClientIds, SelectSelectionInput, SelectionNotify, SelectionRequest, SetInputFocus, SetSelectionOwner, UnmapNotify, XfixesQueryVersion, XfixesSelectionNotify, @@ -56,14 +56,13 @@ use { consts::{ _NET_WM_STATE_ADD, _NET_WM_STATE_REMOVE, _NET_WM_STATE_TOGGLE, ATOM_ATOM, ATOM_NONE, ATOM_STRING, ATOM_WINDOW, ATOM_WM_CLASS, ATOM_WM_NAME, - ATOM_WM_SIZE_HINTS, ATOM_WM_TRANSIENT_FOR, COMPOSITE_REDIRECT_MANUAL, - CONFIG_WINDOW_HEIGHT, CONFIG_WINDOW_WIDTH, CONFIG_WINDOW_X, CONFIG_WINDOW_Y, - EVENT_MASK_FOCUS_CHANGE, EVENT_MASK_PROPERTY_CHANGE, - EVENT_MASK_SUBSTRUCTURE_NOTIFY, EVENT_MASK_SUBSTRUCTURE_REDIRECT, - ICCCM_WM_HINT_INPUT, ICCCM_WM_STATE_ICONIC, ICCCM_WM_STATE_NORMAL, - ICCCM_WM_STATE_WITHDRAWN, INPUT_FOCUS_POINTER_ROOT, MWM_HINTS_DECORATIONS_FIELD, - MWM_HINTS_FLAGS_FIELD, NOTIFY_DETAIL_POINTER, NOTIFY_MODE_GRAB, NOTIFY_MODE_UNGRAB, - PROP_MODE_REPLACE, RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID, SELECTION_CLIENT_CLOSE_MASK, + ATOM_WM_TRANSIENT_FOR, COMPOSITE_REDIRECT_MANUAL, CONFIG_WINDOW_HEIGHT, + CONFIG_WINDOW_WIDTH, CONFIG_WINDOW_X, CONFIG_WINDOW_Y, EVENT_MASK_FOCUS_CHANGE, + EVENT_MASK_PROPERTY_CHANGE, EVENT_MASK_SUBSTRUCTURE_NOTIFY, + EVENT_MASK_SUBSTRUCTURE_REDIRECT, ICCCM_WM_STATE_ICONIC, ICCCM_WM_STATE_NORMAL, + ICCCM_WM_STATE_WITHDRAWN, INPUT_FOCUS_POINTER_ROOT, NOTIFY_DETAIL_POINTER, + NOTIFY_MODE_GRAB, NOTIFY_MODE_UNGRAB, PROP_MODE_REPLACE, + RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID, SELECTION_CLIENT_CLOSE_MASK, SELECTION_WINDOW_DESTROY_MASK, SET_SELECTION_OWNER_MASK, STACK_MODE_ABOVE, STACK_MODE_BELOW, WINDOW_CLASS_INPUT_OUTPUT, }, @@ -71,7 +70,7 @@ use { xwayland::{XWaylandError, XWaylandEvent}, }, ahash::{AHashMap, AHashSet}, - bstr::{ByteSlice, ByteVec}, + bstr::ByteSlice, futures_util::{FutureExt, select}, smallvec::SmallVec, std::{ @@ -838,398 +837,6 @@ impl Wm { } } - fn compute_input_model(&self, data: &Rc) { - let has_wm_take_focus = data.info.protocols.contains(&self.atoms.WM_TAKE_FOCUS); - let accepts_input = data.info.icccm_hints.input.get(); - let model = match (accepts_input, has_wm_take_focus) { - (false, false) => XInputModel::None, - (true, false) => XInputModel::Passive, - (true, true) => XInputModel::Local, - (false, true) => XInputModel::Global, - }; - data.info.input_model.set(model); - } - - async fn load_window_wm_window_role(&self, data: &Rc) { - let property_changed = || { - if let Some(window) = data.window.get() { - window.toplevel_data.property_changed(TL_CHANGED_ROLE); - } - }; - let mut buf = vec![]; - match self - .c - .get_property::(data.window_id, self.atoms.WM_WINDOW_ROLE, 0, &mut buf) - .await - { - Ok(ty) if ty == ATOM_STRING => {} - Ok(ty) if ty == self.atoms.UTF8_STRING => {} - Ok(ty) => { - self.unexpected_type(data.window_id, "WM_WINDOW_ROLE", ty) - .await; - return; - } - Err(XconError::PropertyUnavailable) => { - data.info.role.borrow_mut().take(); - property_changed(); - return; - } - Err(e) => { - log::error!( - "Could not retrieve WM_WINDOW_ROLE property: {}", - ErrorFmt(e) - ); - return; - } - } - // log::info!("{} role {}", data.window_id, buf.as_bstr()); - *data.info.role.borrow_mut() = Some(buf.into_string_lossy()); - property_changed(); - } - - async fn load_window_wm_class(&self, data: &Rc) { - let mut buf = vec![]; - let property_changed = || { - if let Some(window) = data.window.get() { - let class = data.info.class.borrow(); - for handle in window.toplevel_data.manager_handles.lock().values() { - handle.send_app_id(class.as_deref().unwrap_or_default()); - handle.send_done(); - } - window.toplevel_data.property_changed(TL_CHANGED_CLASS_INST); - } - }; - match self - .c - .get_property::(data.window_id, ATOM_WM_CLASS, 0, &mut buf) - .await - { - Ok(ty) if ty == ATOM_STRING => {} - Ok(ty) if ty == self.atoms.UTF8_STRING => {} - Ok(ty) => { - self.unexpected_type(data.window_id, "WM_CLASS", ty).await; - return; - } - Err(XconError::PropertyUnavailable) => { - data.info.instance.borrow_mut().take(); - data.info.class.borrow_mut().take(); - property_changed(); - return; - } - Err(e) => { - log::error!("Could not retrieve WM_CLASS property: {}", ErrorFmt(e)); - return; - } - } - let mut iter = buf.split(|c| *c == 0); - let mut map = || Some(iter.next().unwrap_or(&[]).to_str_lossy().into_owned()); - *data.info.instance.borrow_mut() = map(); - *data.info.class.borrow_mut() = map(); - property_changed(); - } - - async fn load_window_wm_name2(&self, data: &Rc, prop: u32, name: &str) { - let mut buf = vec![]; - match self - .c - .get_property::(data.window_id, prop, 0, &mut buf) - .await - { - Ok(ty) if ty == ATOM_STRING && data.info.utf8_title.get() => return, - Ok(ty) if ty == ATOM_STRING => {} - Ok(ty) if ty == self.atoms.COMPOUND_TEXT => return, // used by java. - Ok(ty) if ty == self.atoms.UTF8_STRING => { - data.info.utf8_title.set(true); - } - Ok(ty) => { - self.unexpected_type(data.window_id, name, ty).await; - return; - } - Err(XconError::PropertyUnavailable) => return, - Err(e) => { - log::error!("Could not retrieve {} property: {}", name, ErrorFmt(e)); - return; - } - } - let title = buf.as_bstr().to_string(); - if let Some(window) = data.window.get() { - window.toplevel_data.set_title(&title); - window.tl_title_changed(); - } - *data.info.title.borrow_mut() = Some(title); - data.title_changed(); - } - - async fn unexpected_type(&self, window: u32, prop: &str, ty: u32) { - let mut ty_name = "unknown".as_bytes().as_bstr(); - let res = self.c.call(&GetAtomName { atom: ty }).await; - if let Ok(res) = &res { - ty_name = res.get().name; - } - log::error!( - "Property {} of window {} has unexpected type {} ({})", - prop, - window, - ty_name, - ty - ); - } - - async fn load_window_wm_name(&self, data: &Rc) { - self.load_window_wm_name2(data, ATOM_WM_NAME, "WM_NAME") - .await; - } - - async fn load_window_net_wm_name(&self, data: &Rc) { - self.load_window_wm_name2(data, self.atoms._NET_WM_NAME, "_NET_WM_NAME") - .await; - } - - async fn load_window_wm_transient_for(&self, data: &Rc) { - let mut buf = vec![]; - if let Err(e) = self - .c - .get_property::(data.window_id, ATOM_WM_TRANSIENT_FOR, ATOM_WINDOW, &mut buf) - .await - { - if not_matches!(e, XconError::PropertyUnavailable) { - log::error!( - "Could not retrieve WM_TRANSIENT_FOR property: {}", - ErrorFmt(e) - ); - } - } - if let Some(old) = data.parent.take() { - old.children.remove(&data.window_id); - } - if let Some(w) = buf.first() - && let Some(w) = self.windows.get(w) - { - if data.is_ancestor_of(w.clone()) { - log::error!("Cannot set WM_TRANSIENT_FOR because it would create a cycle"); - return; - } - w.children.set(data.window_id, data.clone()); - data.parent.set(Some(w.clone())); - } - } - - async fn load_window_wm_protocols(&self, data: &Rc) { - let mut buf = vec![]; - if let Err(e) = self - .c - .get_property::(data.window_id, self.atoms.WM_PROTOCOLS, ATOM_ATOM, &mut buf) - .await - { - if not_matches!(e, XconError::PropertyUnavailable) { - log::error!("Could not retrieve WM_PROTOCOLS property: {}", ErrorFmt(e)); - } - return; - } - data.info.protocols.clear(); - data.info - .protocols - .lock() - .extend(buf.iter().copied().map(|v| (v, ()))); - self.compute_input_model(data); - } - - async fn load_window_wm_hints(&self, data: &Rc) { - let mut buf = vec![]; - if let Err(e) = self - .c - .get_property::(data.window_id, self.atoms.WM_HINTS, 0, &mut buf) - .await - { - if not_matches!(e, XconError::PropertyUnavailable) { - log::error!("Could not retrieve WM_HINTS property: {}", ErrorFmt(e)); - } - data.info.icccm_hints.input.set(true); - self.compute_input_model(data); - return; - } - let mut values = [0; 9]; - let len = values.len().min(buf.len()); - values[..len].copy_from_slice(&buf[..len]); - data.info.icccm_hints.flags.set(values[0] as i32); - data.info.icccm_hints.input.set(values[1] != 0); - data.info.icccm_hints.initial_state.set(values[2] as i32); - data.info.icccm_hints.icon_pixmap.set(values[3]); - data.info.icccm_hints.icon_window.set(values[4]); - data.info.icccm_hints.icon_x.set(values[5] as i32); - data.info.icccm_hints.icon_y.set(values[6] as i32); - data.info.icccm_hints.icon_mask.set(values[7]); - data.info.icccm_hints.window_group.set(values[8]); - if data - .info - .icccm_hints - .flags - .get() - .not_contains(ICCCM_WM_HINT_INPUT) - { - data.info.icccm_hints.input.set(true); - } - self.compute_input_model(data); - } - - async fn load_window_wm_normal_hints(&self, data: &Rc) { - let mut buf = vec![]; - if let Err(e) = self - .c - .get_property::( - data.window_id, - self.atoms.WM_NORMAL_HINTS, - ATOM_WM_SIZE_HINTS, - &mut buf, - ) - .await - { - if not_matches!(e, XconError::PropertyUnavailable) { - log::error!( - "Could not retrieve WM_NORMAL_HINTS property: {}", - ErrorFmt(e) - ); - } - return; - } - let mut values = [0; 18]; - let len = values.len().min(buf.len()); - values[..len].copy_from_slice(&buf[..len]); - data.info.normal_hints.flags.set(values[0]); - data.info.normal_hints.x.set(values[1] as i32); - data.info.normal_hints.y.set(values[2] as i32); - data.info.normal_hints.width.set(values[3] as i32); - data.info.normal_hints.height.set(values[4] as i32); - data.info.normal_hints.min_width.set(values[5] as i32); - data.info.normal_hints.min_height.set(values[6] as i32); - data.info.normal_hints.max_width.set(values[7] as i32); - data.info.normal_hints.max_height.set(values[8] as i32); - data.info.normal_hints.width_inc.set(values[9] as i32); - data.info.normal_hints.height_inc.set(values[10] as i32); - data.info.normal_hints.min_aspect_num.set(values[11] as i32); - data.info.normal_hints.min_aspect_den.set(values[12] as i32); - data.info.normal_hints.max_aspect_num.set(values[13] as i32); - data.info.normal_hints.max_aspect_den.set(values[14] as i32); - data.info.normal_hints.base_width.set(values[15] as i32); - data.info.normal_hints.base_height.set(values[16] as i32); - data.info.normal_hints.win_gravity.set(values[17]); - self.update_wants_floating(data); - } - - async fn load_window_motif_wm_hints(&self, data: &Rc) { - let mut buf = vec![]; - if let Err(e) = self - .c - .get_property::(data.window_id, self.atoms._MOTIF_WM_HINTS, 0, &mut buf) - .await - { - if not_matches!(e, XconError::PropertyUnavailable) { - log::error!( - "Could not retrieve _MOTIF_WM_HINTS property: {}", - ErrorFmt(e) - ); - } - return; - } - let mut values = [0; 5]; - let len = values.len().min(buf.len()); - values[..len].copy_from_slice(&buf[..len]); - data.info - .motif_hints - .flags - .set(values[MWM_HINTS_FLAGS_FIELD]); - data.info - .motif_hints - .decorations - .set(values[MWM_HINTS_DECORATIONS_FIELD]); - } - - async fn load_window_net_startup_id(&self, data: &Rc) { - let mut buf = vec![]; - match self - .c - .get_property::(data.window_id, self.atoms._NET_STARTUP_ID, 0, &mut buf) - .await - { - Ok(ty) if ty == ATOM_STRING => {} - Ok(ty) if ty == self.atoms.UTF8_STRING => {} - Ok(ty) => { - self.unexpected_type(data.window_id, "_NET_STARTUP_ID", ty) - .await; - return; - } - Err(XconError::PropertyUnavailable) => return, - Err(e) => { - log::error!( - "Could not retrieve _NET_STARTUP_ID property: {}", - ErrorFmt(e) - ); - return; - } - } - *data.info.startup_id.borrow_mut() = Some(buf.into()); - } - - async fn load_window_net_wm_state(&self, data: &Rc) { - data.info.fullscreen.set(false); - let mut buf = vec![]; - if let Err(e) = self - .c - .get_property::(data.window_id, self.atoms._NET_WM_STATE, 0, &mut buf) - .await - { - if not_matches!(e, XconError::PropertyUnavailable) { - log::error!("Could not retrieve _NET_WM_STATE property: {}", ErrorFmt(e)); - } - return; - } - for prop in buf { - if prop == self.atoms._NET_WM_STATE_MODAL { - data.info.modal.set(true); - self.update_wants_floating(data); - } else if prop == self.atoms._NET_WM_STATE_FULLSCREEN { - data.info.fullscreen.set(true); - } else if prop == self.atoms._NET_WM_STATE_MAXIMIZED_VERT { - data.info.maximized_vert.set(true); - } else if prop == self.atoms._NET_WM_STATE_MAXIMIZED_HORZ { - data.info.maximized_horz.set(true); - } else if prop == self.atoms._NET_WM_STATE_HIDDEN { - data.info.minimized.set(true); - } - } - } - - async fn load_window_net_wm_window_type(&self, data: &Rc) { - let mut buf = vec![]; - if let Err(e) = self - .c - .get_property::( - data.window_id, - self.atoms._NET_WM_WINDOW_TYPE, - ATOM_ATOM, - &mut buf, - ) - .await - { - if not_matches!(e, XconError::PropertyUnavailable) { - log::error!( - "Could not retrieve _NET_WM_WINDOW_TYPE property: {}", - ErrorFmt(e) - ); - } - return; - } - data.info - .never_focus - .set(buf.iter().any(|t| self.never_focus.contains(t))); - data.info.window_types.clear(); - data.info - .window_types - .lock() - .extend(buf.iter().copied().map(|v| (v, ()))); - self.update_wants_floating(data); - } - async fn create_window(&mut self, data: &Rc, surface: Rc) { if data.window.is_some() { log::error!("The xwindow has already been constructed"); diff --git a/src/xwayland/xwm/properties.rs b/src/xwayland/xwm/properties.rs new file mode 100644 index 00000000..fc8cf48d --- /dev/null +++ b/src/xwayland/xwm/properties.rs @@ -0,0 +1,413 @@ +use { + super::Wm, + crate::{ + criteria::tlm::{TL_CHANGED_CLASS_INST, TL_CHANGED_ROLE}, + ifs::wl_surface::x_surface::xwindow::{XInputModel, XwindowData}, + tree::ToplevelNode, + utils::{bitflags::BitflagsExt, errorfmt::ErrorFmt}, + wire_xcon::GetAtomName, + xcon::{ + XconError, + consts::{ + ATOM_ATOM, ATOM_STRING, ATOM_WINDOW, ATOM_WM_CLASS, ATOM_WM_NAME, + ATOM_WM_SIZE_HINTS, ATOM_WM_TRANSIENT_FOR, ICCCM_WM_HINT_INPUT, + MWM_HINTS_DECORATIONS_FIELD, MWM_HINTS_FLAGS_FIELD, + }, + }, + }, + bstr::{ByteSlice, ByteVec}, + std::rc::Rc, +}; + +impl Wm { + pub(super) fn compute_input_model(&self, data: &Rc) { + let has_wm_take_focus = data.info.protocols.contains(&self.atoms.WM_TAKE_FOCUS); + let accepts_input = data.info.icccm_hints.input.get(); + let model = match (accepts_input, has_wm_take_focus) { + (false, false) => XInputModel::None, + (true, false) => XInputModel::Passive, + (true, true) => XInputModel::Local, + (false, true) => XInputModel::Global, + }; + data.info.input_model.set(model); + } + + pub(super) async fn load_window_wm_window_role(&self, data: &Rc) { + let property_changed = || { + if let Some(window) = data.window.get() { + window.toplevel_data.property_changed(TL_CHANGED_ROLE); + } + }; + let mut buf = vec![]; + match self + .c + .get_property::(data.window_id, self.atoms.WM_WINDOW_ROLE, 0, &mut buf) + .await + { + Ok(ty) if ty == ATOM_STRING => {} + Ok(ty) if ty == self.atoms.UTF8_STRING => {} + Ok(ty) => { + self.unexpected_type(data.window_id, "WM_WINDOW_ROLE", ty) + .await; + return; + } + Err(XconError::PropertyUnavailable) => { + data.info.role.borrow_mut().take(); + property_changed(); + return; + } + Err(e) => { + log::error!( + "Could not retrieve WM_WINDOW_ROLE property: {}", + ErrorFmt(e) + ); + return; + } + } + *data.info.role.borrow_mut() = Some(buf.into_string_lossy()); + property_changed(); + } + + pub(super) async fn load_window_wm_class(&self, data: &Rc) { + let mut buf = vec![]; + let property_changed = || { + if let Some(window) = data.window.get() { + let class = data.info.class.borrow(); + for handle in window.toplevel_data.manager_handles.lock().values() { + handle.send_app_id(class.as_deref().unwrap_or_default()); + handle.send_done(); + } + window.toplevel_data.property_changed(TL_CHANGED_CLASS_INST); + } + }; + match self + .c + .get_property::(data.window_id, ATOM_WM_CLASS, 0, &mut buf) + .await + { + Ok(ty) if ty == ATOM_STRING => {} + Ok(ty) if ty == self.atoms.UTF8_STRING => {} + Ok(ty) => { + self.unexpected_type(data.window_id, "WM_CLASS", ty).await; + return; + } + Err(XconError::PropertyUnavailable) => { + data.info.instance.borrow_mut().take(); + data.info.class.borrow_mut().take(); + property_changed(); + return; + } + Err(e) => { + log::error!("Could not retrieve WM_CLASS property: {}", ErrorFmt(e)); + return; + } + } + let mut iter = buf.split(|c| *c == 0); + let mut map = || Some(iter.next().unwrap_or(&[]).to_str_lossy().into_owned()); + *data.info.instance.borrow_mut() = map(); + *data.info.class.borrow_mut() = map(); + property_changed(); + } + + async fn load_window_wm_name2(&self, data: &Rc, prop: u32, name: &str) { + let mut buf = vec![]; + match self + .c + .get_property::(data.window_id, prop, 0, &mut buf) + .await + { + Ok(ty) if ty == ATOM_STRING && data.info.utf8_title.get() => return, + Ok(ty) if ty == ATOM_STRING => {} + Ok(ty) if ty == self.atoms.COMPOUND_TEXT => return, + Ok(ty) if ty == self.atoms.UTF8_STRING => { + data.info.utf8_title.set(true); + } + Ok(ty) => { + self.unexpected_type(data.window_id, name, ty).await; + return; + } + Err(XconError::PropertyUnavailable) => return, + Err(e) => { + log::error!("Could not retrieve {} property: {}", name, ErrorFmt(e)); + return; + } + } + let title = buf.as_bstr().to_string(); + if let Some(window) = data.window.get() { + window.toplevel_data.set_title(&title); + window.tl_title_changed(); + } + *data.info.title.borrow_mut() = Some(title); + data.title_changed(); + } + + async fn unexpected_type(&self, window: u32, prop: &str, ty: u32) { + let mut ty_name = "unknown".as_bytes().as_bstr(); + let res = self.c.call(&GetAtomName { atom: ty }).await; + if let Ok(res) = &res { + ty_name = res.get().name; + } + log::error!( + "Property {} of window {} has unexpected type {} ({})", + prop, + window, + ty_name, + ty + ); + } + + pub(super) async fn load_window_wm_name(&self, data: &Rc) { + self.load_window_wm_name2(data, ATOM_WM_NAME, "WM_NAME") + .await; + } + + pub(super) async fn load_window_net_wm_name(&self, data: &Rc) { + self.load_window_wm_name2(data, self.atoms._NET_WM_NAME, "_NET_WM_NAME") + .await; + } + + pub(super) async fn load_window_wm_transient_for(&self, data: &Rc) { + let mut buf = vec![]; + if let Err(e) = self + .c + .get_property::(data.window_id, ATOM_WM_TRANSIENT_FOR, ATOM_WINDOW, &mut buf) + .await + { + if not_matches!(e, XconError::PropertyUnavailable) { + log::error!( + "Could not retrieve WM_TRANSIENT_FOR property: {}", + ErrorFmt(e) + ); + } + } + if let Some(old) = data.parent.take() { + old.children.remove(&data.window_id); + } + if let Some(w) = buf.first() + && let Some(w) = self.windows.get(w) + { + if data.is_ancestor_of(w.clone()) { + log::error!("Cannot set WM_TRANSIENT_FOR because it would create a cycle"); + return; + } + w.children.set(data.window_id, data.clone()); + data.parent.set(Some(w.clone())); + } + } + + pub(super) async fn load_window_wm_protocols(&self, data: &Rc) { + let mut buf = vec![]; + if let Err(e) = self + .c + .get_property::(data.window_id, self.atoms.WM_PROTOCOLS, ATOM_ATOM, &mut buf) + .await + { + if not_matches!(e, XconError::PropertyUnavailable) { + log::error!("Could not retrieve WM_PROTOCOLS property: {}", ErrorFmt(e)); + } + return; + } + data.info.protocols.clear(); + data.info + .protocols + .lock() + .extend(buf.iter().copied().map(|v| (v, ()))); + self.compute_input_model(data); + } + + pub(super) async fn load_window_wm_hints(&self, data: &Rc) { + let mut buf = vec![]; + if let Err(e) = self + .c + .get_property::(data.window_id, self.atoms.WM_HINTS, 0, &mut buf) + .await + { + if not_matches!(e, XconError::PropertyUnavailable) { + log::error!("Could not retrieve WM_HINTS property: {}", ErrorFmt(e)); + } + data.info.icccm_hints.input.set(true); + self.compute_input_model(data); + return; + } + let mut values = [0; 9]; + let len = values.len().min(buf.len()); + values[..len].copy_from_slice(&buf[..len]); + data.info.icccm_hints.flags.set(values[0] as i32); + data.info.icccm_hints.input.set(values[1] != 0); + data.info.icccm_hints.initial_state.set(values[2] as i32); + data.info.icccm_hints.icon_pixmap.set(values[3]); + data.info.icccm_hints.icon_window.set(values[4]); + data.info.icccm_hints.icon_x.set(values[5] as i32); + data.info.icccm_hints.icon_y.set(values[6] as i32); + data.info.icccm_hints.icon_mask.set(values[7]); + data.info.icccm_hints.window_group.set(values[8]); + if data + .info + .icccm_hints + .flags + .get() + .not_contains(ICCCM_WM_HINT_INPUT) + { + data.info.icccm_hints.input.set(true); + } + self.compute_input_model(data); + } + + pub(super) async fn load_window_wm_normal_hints(&self, data: &Rc) { + let mut buf = vec![]; + if let Err(e) = self + .c + .get_property::( + data.window_id, + self.atoms.WM_NORMAL_HINTS, + ATOM_WM_SIZE_HINTS, + &mut buf, + ) + .await + { + if not_matches!(e, XconError::PropertyUnavailable) { + log::error!( + "Could not retrieve WM_NORMAL_HINTS property: {}", + ErrorFmt(e) + ); + } + return; + } + let mut values = [0; 18]; + let len = values.len().min(buf.len()); + values[..len].copy_from_slice(&buf[..len]); + data.info.normal_hints.flags.set(values[0]); + data.info.normal_hints.x.set(values[1] as i32); + data.info.normal_hints.y.set(values[2] as i32); + data.info.normal_hints.width.set(values[3] as i32); + data.info.normal_hints.height.set(values[4] as i32); + data.info.normal_hints.min_width.set(values[5] as i32); + data.info.normal_hints.min_height.set(values[6] as i32); + data.info.normal_hints.max_width.set(values[7] as i32); + data.info.normal_hints.max_height.set(values[8] as i32); + data.info.normal_hints.width_inc.set(values[9] as i32); + data.info.normal_hints.height_inc.set(values[10] as i32); + data.info.normal_hints.min_aspect_num.set(values[11] as i32); + data.info.normal_hints.min_aspect_den.set(values[12] as i32); + data.info.normal_hints.max_aspect_num.set(values[13] as i32); + data.info.normal_hints.max_aspect_den.set(values[14] as i32); + data.info.normal_hints.base_width.set(values[15] as i32); + data.info.normal_hints.base_height.set(values[16] as i32); + data.info.normal_hints.win_gravity.set(values[17]); + self.update_wants_floating(data); + } + + pub(super) async fn load_window_motif_wm_hints(&self, data: &Rc) { + let mut buf = vec![]; + if let Err(e) = self + .c + .get_property::(data.window_id, self.atoms._MOTIF_WM_HINTS, 0, &mut buf) + .await + { + if not_matches!(e, XconError::PropertyUnavailable) { + log::error!( + "Could not retrieve _MOTIF_WM_HINTS property: {}", + ErrorFmt(e) + ); + } + return; + } + let mut values = [0; 5]; + let len = values.len().min(buf.len()); + values[..len].copy_from_slice(&buf[..len]); + data.info + .motif_hints + .flags + .set(values[MWM_HINTS_FLAGS_FIELD]); + data.info + .motif_hints + .decorations + .set(values[MWM_HINTS_DECORATIONS_FIELD]); + } + + pub(super) async fn load_window_net_startup_id(&self, data: &Rc) { + let mut buf = vec![]; + match self + .c + .get_property::(data.window_id, self.atoms._NET_STARTUP_ID, 0, &mut buf) + .await + { + Ok(ty) if ty == ATOM_STRING => {} + Ok(ty) if ty == self.atoms.UTF8_STRING => {} + Ok(ty) => { + self.unexpected_type(data.window_id, "_NET_STARTUP_ID", ty) + .await; + return; + } + Err(XconError::PropertyUnavailable) => return, + Err(e) => { + log::error!( + "Could not retrieve _NET_STARTUP_ID property: {}", + ErrorFmt(e) + ); + return; + } + } + *data.info.startup_id.borrow_mut() = Some(buf.into()); + } + + pub(super) async fn load_window_net_wm_state(&self, data: &Rc) { + data.info.fullscreen.set(false); + let mut buf = vec![]; + if let Err(e) = self + .c + .get_property::(data.window_id, self.atoms._NET_WM_STATE, 0, &mut buf) + .await + { + if not_matches!(e, XconError::PropertyUnavailable) { + log::error!("Could not retrieve _NET_WM_STATE property: {}", ErrorFmt(e)); + } + return; + } + for prop in buf { + if prop == self.atoms._NET_WM_STATE_MODAL { + data.info.modal.set(true); + self.update_wants_floating(data); + } else if prop == self.atoms._NET_WM_STATE_FULLSCREEN { + data.info.fullscreen.set(true); + } else if prop == self.atoms._NET_WM_STATE_MAXIMIZED_VERT { + data.info.maximized_vert.set(true); + } else if prop == self.atoms._NET_WM_STATE_MAXIMIZED_HORZ { + data.info.maximized_horz.set(true); + } else if prop == self.atoms._NET_WM_STATE_HIDDEN { + data.info.minimized.set(true); + } + } + } + + pub(super) async fn load_window_net_wm_window_type(&self, data: &Rc) { + let mut buf = vec![]; + if let Err(e) = self + .c + .get_property::( + data.window_id, + self.atoms._NET_WM_WINDOW_TYPE, + ATOM_ATOM, + &mut buf, + ) + .await + { + if not_matches!(e, XconError::PropertyUnavailable) { + log::error!( + "Could not retrieve _NET_WM_WINDOW_TYPE property: {}", + ErrorFmt(e) + ); + } + return; + } + data.info + .never_focus + .set(buf.iter().any(|t| self.never_focus.contains(t))); + data.info.window_types.clear(); + data.info + .window_types + .lock() + .extend(buf.iter().copied().map(|v| (v, ()))); + self.update_wants_floating(data); + } +} diff --git a/src/xwayland/xwm/selection.rs b/src/xwayland/xwm/selection.rs index 0bbaa1ea..7102cd09 100644 --- a/src/xwayland/xwm/selection.rs +++ b/src/xwayland/xwm/selection.rs @@ -1,4 +1,8 @@ -use {super::*, super::transfer::{WaylandToXTransfer, XToWaylandTransfer}}; +use { + super::*, + super::transfer::{WaylandToXTransfer, XToWaylandTransfer}, + crate::wire_xcon::GetAtomName, +}; pub(super) struct EnhancedOffer { offer: Rc,