2022-02-04 21:04:17 +01:00
// SPDX-License-Identifier: GPL-3.0-only
2023-02-25 00:17:54 +01:00
use std ::{
borrow ::{ Borrow , BorrowMut } ,
cell ::RefCell ,
2023-05-17 19:46:21 +02:00
collections ::HashMap ,
sync ::Weak ,
2023-05-22 20:19:11 +02:00
time ::Instant ,
2023-02-25 00:17:54 +01:00
} ;
2023-08-01 14:26:51 -07:00
#[ cfg(feature = " debug " ) ]
use crate ::debug ::{ fps_ui , profiler_ui } ;
2022-02-04 21:04:17 +01:00
use crate ::{
2023-05-22 20:19:11 +02:00
config ::WorkspaceLayout ,
2023-03-07 16:37:11 +01:00
shell ::{
2023-07-17 21:11:23 +02:00
focus ::target ::WindowGroup , grabs ::SeatMoveGrabState , layout ::tiling ::ANIMATION_DURATION ,
2023-09-08 22:16:39 +02:00
CosmicMapped , CosmicMappedRenderElement , OverviewMode , Trigger , WorkspaceRenderElement ,
2023-03-07 16:37:11 +01:00
} ,
2022-11-22 18:20:20 +01:00
state ::{ Common , Fps } ,
2023-05-22 20:19:11 +02:00
utils ::prelude ::{ OutputExt , SeatExt } ,
2022-11-03 18:51:27 +01:00
wayland ::{
2023-05-22 20:19:11 +02:00
handlers ::{
data_device ::get_dnd_icon ,
screencopy ::{ render_session , WORKSPACE_OVERVIEW_NAMESPACE } ,
} ,
2022-11-03 18:51:27 +01:00
protocols ::{
2022-11-04 17:34:17 +01:00
screencopy ::{
BufferParams , CursorMode as ScreencopyCursorMode , Session as ScreencopySession ,
} ,
2022-11-03 18:51:27 +01:00
workspace ::WorkspaceHandle ,
} ,
} ,
2022-08-30 13:28:36 +02:00
} ;
2022-02-04 21:04:17 +01:00
2022-11-03 18:51:27 +01:00
use cosmic_protocols ::screencopy ::v1 ::server ::zcosmic_screencopy_session_v1 ::FailureReason ;
2023-07-11 17:12:56 +02:00
use keyframe ::{ ease , functions ::EaseInOutCubic } ;
2022-02-04 21:04:17 +01:00
use smithay ::{
2022-03-22 12:36:03 +01:00
backend ::{
2022-11-03 18:51:27 +01:00
allocator ::dmabuf ::Dmabuf ,
2022-03-22 12:36:03 +01:00
drm ::DrmNode ,
renderer ::{
2022-11-17 20:32:54 +01:00
buffer_dimensions ,
2023-07-31 19:12:33 +02:00
damage ::{ Error as RenderError , OutputDamageTracker , RenderOutputResult } ,
2023-05-22 20:19:11 +02:00
element ::{
2023-07-13 17:19:29 +02:00
surface ::render_elements_from_surface_tree ,
2023-05-22 20:19:11 +02:00
utils ::{ Relocate , RelocateRenderElement } ,
2023-09-13 20:24:11 -07:00
Element , Id , Kind , RenderElement ,
2023-05-22 20:19:11 +02:00
} ,
2023-04-18 17:10:21 +02:00
gles ::{
element ::PixelShaderElement , GlesError , GlesPixelProgram , GlesRenderer , Uniform ,
2023-02-25 00:17:54 +01:00
UniformName , UniformType ,
} ,
2022-11-17 20:32:54 +01:00
glow ::GlowRenderer ,
2023-05-19 19:44:57 +02:00
multigpu ::{ gbm ::GbmGlesBackend , Error as MultiError , MultiFrame , MultiRenderer } ,
2023-06-28 22:20:06 +02:00
sync ::SyncPoint ,
2022-11-03 18:51:27 +01:00
Bind , Blit , ExportMem , ImportAll , ImportMem , Offscreen , Renderer , TextureFilter ,
2022-03-22 12:36:03 +01:00
} ,
2022-03-16 20:05:24 +01:00
} ,
2023-07-13 17:19:29 +02:00
desktop ::{ layer_map_for_output , PopupManager } ,
2023-07-31 19:12:33 +02:00
output ::{ Output , OutputNoMode } ,
2023-06-28 22:20:06 +02:00
utils ::{ IsAlive , Logical , Point , Rectangle , Scale } ,
2023-04-18 17:10:21 +02:00
wayland ::{
dmabuf ::get_dmabuf ,
2023-05-22 20:19:11 +02:00
shell ::wlr_layer ::Layer ,
2023-04-18 17:10:21 +02:00
shm ::{ shm_format_to_fourcc , with_buffer_contents } ,
} ,
2022-02-04 21:04:17 +01:00
} ;
2023-02-24 17:41:52 +01:00
use tracing ::warn ;
2022-02-04 21:04:17 +01:00
2022-08-05 14:28:37 +02:00
pub mod cursor ;
2022-09-28 12:01:29 +02:00
use self ::cursor ::CursorRenderElement ;
2022-11-17 20:32:54 +01:00
pub mod element ;
2022-11-28 17:48:50 +01:00
use self ::element ::{ AsGlowRenderer , CosmicElement } ;
2022-02-04 21:04:17 +01:00
2023-02-13 17:44:24 +01:00
pub type GlMultiRenderer < ' a , ' b > =
MultiRenderer < ' a , ' a , ' b , GbmGlesBackend < GlowRenderer > , GbmGlesBackend < GlowRenderer > > ;
pub type GlMultiFrame < ' a , ' b , ' frame > =
MultiFrame < ' a , ' a , ' b , ' frame , GbmGlesBackend < GlowRenderer > , GbmGlesBackend < GlowRenderer > > ;
2023-05-19 19:44:57 +02:00
pub type GlMultiError = MultiError < GbmGlesBackend < GlowRenderer > , GbmGlesBackend < GlowRenderer > > ;
2022-03-16 20:06:31 +01:00
2022-11-03 18:51:27 +01:00
pub static CLEAR_COLOR : [ f32 ; 4 ] = [ 0.153 , 0.161 , 0.165 , 1.0 ] ;
2023-05-30 13:20:46 +02:00
pub static GROUP_COLOR : [ f32 ; 3 ] = [ 0.788 , 0.788 , 0.788 ] ;
2023-07-24 19:31:31 +02:00
pub static ACTIVE_GROUP_COLOR : [ f32 ; 3 ] = [ 0.58 , 0.922 , 0.922 ] ;
2023-05-12 20:01:37 +02:00
pub static FOCUS_INDICATOR_COLOR : [ f32 ; 3 ] = [ 0.580 , 0.921 , 0.921 ] ;
2023-05-26 20:51:10 +02:00
pub static OUTLINE_SHADER : & str = include_str! ( " ./shaders/rounded_outline.frag " ) ;
pub static RECTANGLE_SHADER : & str = include_str! ( " ./shaders/rounded_rectangle.frag " ) ;
2023-02-25 00:17:54 +01:00
2023-04-18 17:10:21 +02:00
pub struct IndicatorShader ( pub GlesPixelProgram ) ;
2023-05-17 19:46:21 +02:00
2023-08-11 18:15:22 +02:00
#[ derive(Debug, Clone, Copy, Hash, PartialEq, Eq) ]
pub enum Usage {
OverviewBackdrop ,
Overlay ,
MoveGrabIndicator ,
FocusIndicator ,
PotentialGroupIndicator ,
}
2023-05-17 19:46:21 +02:00
#[ derive(Clone) ]
2023-05-31 13:27:52 +02:00
pub enum Key {
Static ( Id ) ,
2023-05-17 19:46:21 +02:00
Group ( Weak < ( ) > ) ,
2023-08-11 18:15:22 +02:00
Window ( Usage , CosmicMapped ) ,
2023-05-17 19:46:21 +02:00
}
2023-05-31 13:27:52 +02:00
impl std ::hash ::Hash for Key {
2023-05-17 19:46:21 +02:00
fn hash < H : std ::hash ::Hasher > ( & self , state : & mut H ) {
match self {
2023-05-31 13:27:52 +02:00
Key ::Static ( id ) = > id . hash ( state ) ,
Key ::Group ( arc ) = > ( arc . as_ptr ( ) as usize ) . hash ( state ) ,
2023-08-11 18:15:22 +02:00
Key ::Window ( usage , window ) = > {
usage . hash ( state ) ;
window . hash ( state ) ;
}
2023-05-17 19:46:21 +02:00
}
}
}
2023-05-31 13:27:52 +02:00
impl PartialEq for Key {
2023-05-17 19:46:21 +02:00
fn eq ( & self , other : & Self ) -> bool {
match ( self , other ) {
2023-05-31 13:27:52 +02:00
( Key ::Static ( s1 ) , Key ::Static ( s2 ) ) = > s1 = = s2 ,
( Key ::Group ( g1 ) , Key ::Group ( g2 ) ) = > Weak ::ptr_eq ( g1 , g2 ) ,
2023-08-11 18:15:22 +02:00
( Key ::Window ( u1 , w1 ) , Key ::Window ( u2 , w2 ) ) = > u1 = = u2 & & w1 = = w2 ,
2023-05-17 19:46:21 +02:00
_ = > false ,
}
}
}
2023-05-31 13:27:52 +02:00
impl Eq for Key { }
impl From < WindowGroup > for Key {
2023-05-17 19:46:21 +02:00
fn from ( group : WindowGroup ) -> Self {
2023-05-31 13:27:52 +02:00
Key ::Group ( group . alive . clone ( ) )
}
}
impl From < Id > for Key {
fn from ( id : Id ) -> Self {
Key ::Static ( id )
2023-05-17 19:46:21 +02:00
}
}
#[ derive(PartialEq) ]
struct IndicatorSettings {
thickness : u8 ,
2023-05-30 13:20:46 +02:00
radius : u8 ,
2023-05-17 19:46:21 +02:00
alpha : f32 ,
color : [ f32 ; 3 ] ,
}
2023-05-31 13:27:52 +02:00
type IndicatorCache = RefCell < HashMap < Key , ( IndicatorSettings , PixelShaderElement ) > > ;
2023-02-25 00:17:54 +01:00
impl IndicatorShader {
2023-04-18 17:10:21 +02:00
pub fn get < R : AsGlowRenderer > ( renderer : & R ) -> GlesPixelProgram {
Borrow ::< GlesRenderer > ::borrow ( renderer . glow_renderer ( ) )
2023-02-25 00:17:54 +01:00
. egl_context ( )
. user_data ( )
. get ::< IndicatorShader > ( )
. expect ( " Custom Shaders not initialized " )
. 0
. clone ( )
}
2023-05-25 21:36:44 +02:00
pub fn focus_element < R : AsGlowRenderer > (
renderer : & R ,
2023-05-31 13:27:52 +02:00
key : impl Into < Key > ,
2023-05-25 21:36:44 +02:00
mut element_geo : Rectangle < i32 , Logical > ,
thickness : u8 ,
2023-08-31 18:17:37 +02:00
scale : f64 ,
2023-05-25 21:36:44 +02:00
alpha : f32 ,
) -> PixelShaderElement {
let t = thickness as i32 ;
element_geo . loc - = ( t , t ) . into ( ) ;
element_geo . size + = ( t * 2 , t * 2 ) . into ( ) ;
2023-05-30 13:20:46 +02:00
IndicatorShader ::element (
renderer ,
key ,
element_geo ,
thickness ,
thickness * 2 ,
alpha ,
2023-08-31 18:17:37 +02:00
scale ,
2023-05-30 13:20:46 +02:00
FOCUS_INDICATOR_COLOR ,
)
2023-05-25 21:36:44 +02:00
}
2023-02-25 00:17:54 +01:00
pub fn element < R : AsGlowRenderer > (
renderer : & R ,
2023-05-31 13:27:52 +02:00
key : impl Into < Key > ,
2023-02-25 00:17:54 +01:00
geo : Rectangle < i32 , Logical > ,
2023-03-09 18:27:11 +01:00
thickness : u8 ,
2023-05-30 13:20:46 +02:00
radius : u8 ,
2023-05-12 20:01:37 +02:00
alpha : f32 ,
2023-08-31 18:17:37 +02:00
scale : f64 ,
2023-05-17 19:46:21 +02:00
color : [ f32 ; 3 ] ,
2023-02-25 00:17:54 +01:00
) -> PixelShaderElement {
2023-08-31 18:17:37 +02:00
let thickness = ( thickness as f64 * scale ) . round ( ) as u8 ;
2023-05-17 19:46:21 +02:00
let settings = IndicatorSettings {
thickness ,
2023-05-30 13:20:46 +02:00
radius ,
2023-05-17 19:46:21 +02:00
alpha ,
color ,
} ;
2023-02-25 00:17:54 +01:00
2023-04-18 17:10:21 +02:00
let user_data = Borrow ::< GlesRenderer > ::borrow ( renderer . glow_renderer ( ) )
2023-02-25 00:17:54 +01:00
. egl_context ( )
. user_data ( ) ;
2023-05-17 19:46:21 +02:00
user_data . insert_if_missing ( | | IndicatorCache ::new ( HashMap ::new ( ) ) ) ;
let mut cache = user_data . get ::< IndicatorCache > ( ) . unwrap ( ) . borrow_mut ( ) ;
cache . retain ( | k , _ | match k {
2023-05-31 13:27:52 +02:00
Key ::Static ( _ ) = > true ,
Key ::Group ( w ) = > w . upgrade ( ) . is_some ( ) ,
2023-08-11 18:15:22 +02:00
Key ::Window ( _ , w ) = > w . alive ( ) ,
2023-05-17 19:46:21 +02:00
} ) ;
let key = key . into ( ) ;
if cache
. get ( & key )
. filter ( | ( old_settings , _ ) | & settings = = old_settings )
. is_none ( )
{
let thickness : f32 = thickness as f32 ;
let shader = Self ::get ( renderer ) ;
let elem = PixelShaderElement ::new (
shader ,
geo ,
None , //TODO
alpha ,
vec! [
2023-06-01 17:03:55 +02:00
Uniform ::new (
" color " ,
[ color [ 0 ] * alpha , color [ 1 ] * alpha , color [ 2 ] * alpha ] ,
) ,
2023-05-17 19:46:21 +02:00
Uniform ::new ( " thickness " , thickness ) ,
2023-05-30 13:20:46 +02:00
Uniform ::new ( " radius " , radius as f32 ) ,
2023-05-17 19:46:21 +02:00
] ,
2023-09-13 20:24:11 -07:00
Kind ::Unspecified ,
2023-05-17 19:46:21 +02:00
) ;
cache . insert ( key . clone ( ) , ( settings , elem ) ) ;
}
let elem = & mut cache . get_mut ( & key ) . unwrap ( ) . 1 ;
if elem . geometry ( 1. 0. into ( ) ) . to_logical ( 1 ) ! = geo {
elem . resize ( geo , None ) ;
2023-02-25 00:17:54 +01:00
}
2023-05-17 19:46:21 +02:00
elem . clone ( )
2023-02-25 00:17:54 +01:00
}
}
2023-05-26 20:51:10 +02:00
pub struct BackdropShader ( pub GlesPixelProgram ) ;
#[ derive(PartialEq) ]
struct BackdropSettings {
radius : f32 ,
alpha : f32 ,
color : [ f32 ; 3 ] ,
}
2023-05-31 13:27:52 +02:00
type BackdropCache = RefCell < HashMap < Key , ( BackdropSettings , PixelShaderElement ) > > ;
2023-05-26 20:51:10 +02:00
impl BackdropShader {
pub fn get < R : AsGlowRenderer > ( renderer : & R ) -> GlesPixelProgram {
Borrow ::< GlesRenderer > ::borrow ( renderer . glow_renderer ( ) )
. egl_context ( )
. user_data ( )
. get ::< BackdropShader > ( )
. expect ( " Custom Shaders not initialized " )
. 0
. clone ( )
}
pub fn element < R : AsGlowRenderer > (
renderer : & R ,
2023-05-31 13:27:52 +02:00
key : impl Into < Key > ,
2023-05-26 20:51:10 +02:00
geo : Rectangle < i32 , Logical > ,
radius : f32 ,
alpha : f32 ,
color : [ f32 ; 3 ] ,
) -> PixelShaderElement {
let settings = BackdropSettings {
radius ,
alpha ,
color ,
} ;
let user_data = Borrow ::< GlesRenderer > ::borrow ( renderer . glow_renderer ( ) )
. egl_context ( )
. user_data ( ) ;
user_data . insert_if_missing ( | | BackdropCache ::new ( HashMap ::new ( ) ) ) ;
let mut cache = user_data . get ::< BackdropCache > ( ) . unwrap ( ) . borrow_mut ( ) ;
cache . retain ( | k , _ | match k {
2023-05-31 13:27:52 +02:00
Key ::Static ( _ ) = > true ,
Key ::Group ( a ) = > a . upgrade ( ) . is_some ( ) ,
2023-08-11 18:15:22 +02:00
Key ::Window ( _ , w ) = > w . alive ( ) ,
2023-05-26 20:51:10 +02:00
} ) ;
let key = key . into ( ) ;
if cache
. get ( & key )
. filter ( | ( old_settings , _ ) | & settings = = old_settings )
. is_none ( )
{
let shader = Self ::get ( renderer ) ;
let elem = PixelShaderElement ::new (
shader ,
geo ,
None , // TODO
alpha ,
2023-06-01 17:03:55 +02:00
vec! [
Uniform ::new (
" color " ,
[ color [ 0 ] * alpha , color [ 1 ] * alpha , color [ 2 ] * alpha ] ,
) ,
Uniform ::new ( " radius " , radius ) ,
] ,
2023-09-13 20:24:11 -07:00
Kind ::Unspecified ,
2023-05-26 20:51:10 +02:00
) ;
cache . insert ( key . clone ( ) , ( settings , elem ) ) ;
}
let elem = & mut cache . get_mut ( & key ) . unwrap ( ) . 1 ;
if elem . geometry ( 1. 0. into ( ) ) . to_logical ( 1 ) ! = geo {
elem . resize ( geo , None ) ;
}
elem . clone ( )
}
}
2023-04-18 17:10:21 +02:00
pub fn init_shaders < R : AsGlowRenderer > ( renderer : & mut R ) -> Result < ( ) , GlesError > {
2023-02-25 00:17:54 +01:00
let glow_renderer = renderer . glow_renderer_mut ( ) ;
2023-04-18 17:10:21 +02:00
let gles_renderer : & mut GlesRenderer = glow_renderer . borrow_mut ( ) ;
2023-02-25 00:17:54 +01:00
2023-05-26 20:51:10 +02:00
let outline_shader = gles_renderer . compile_custom_pixel_shader (
OUTLINE_SHADER ,
2023-02-25 00:17:54 +01:00
& [
UniformName ::new ( " color " , UniformType ::_3f ) ,
UniformName ::new ( " thickness " , UniformType ::_1f ) ,
UniformName ::new ( " radius " , UniformType ::_1f ) ,
] ,
) ? ;
2023-05-26 20:51:10 +02:00
let rectangle_shader = gles_renderer . compile_custom_pixel_shader (
RECTANGLE_SHADER ,
& [
UniformName ::new ( " color " , UniformType ::_3f ) ,
UniformName ::new ( " radius " , UniformType ::_1f ) ,
] ,
) ? ;
2023-02-25 00:17:54 +01:00
let egl_context = gles_renderer . egl_context ( ) ;
egl_context
. user_data ( )
2023-05-26 20:51:10 +02:00
. insert_if_missing ( | | IndicatorShader ( outline_shader ) ) ;
egl_context
. user_data ( )
. insert_if_missing ( | | BackdropShader ( rectangle_shader ) ) ;
2023-02-25 00:17:54 +01:00
Ok ( ( ) )
}
2022-04-22 15:18:28 +02:00
2022-11-03 18:51:27 +01:00
#[ derive(Debug, Clone, Copy, PartialEq, Eq) ]
pub enum CursorMode {
None ,
NotDefault ,
All ,
}
2022-11-28 17:48:50 +01:00
pub fn cursor_elements < ' frame , E , R > (
2022-08-05 16:28:05 +02:00
renderer : & mut R ,
state : & Common ,
output : & Output ,
2022-11-03 18:51:27 +01:00
mode : CursorMode ,
2022-09-28 12:01:29 +02:00
) -> Vec < E >
2022-08-05 16:28:05 +02:00
where
2022-11-22 10:28:30 +01:00
R : Renderer + ImportAll + ImportMem + AsGlowRenderer ,
2022-09-28 12:01:29 +02:00
< R as Renderer > ::TextureId : Clone + 'static ,
2022-11-22 10:28:30 +01:00
CosmicMappedRenderElement < R > : RenderElement < R > ,
2022-10-26 15:26:07 +02:00
E : From < CursorRenderElement < R > > + From < CosmicMappedRenderElement < R > > ,
2022-08-05 16:28:05 +02:00
{
2023-03-06 18:50:11 +01:00
#[ cfg(feature = " debug " ) ]
puffin ::profile_function! ( ) ;
2022-09-28 12:01:29 +02:00
let scale = output . current_scale ( ) . fractional_scale ( ) ;
let mut elements = Vec ::new ( ) ;
2022-08-05 16:28:05 +02:00
2022-09-28 12:01:29 +02:00
for seat in state . seats ( ) {
2022-08-05 16:28:05 +02:00
let pointer = match seat . get_pointer ( ) {
Some ( ptr ) = > ptr ,
None = > continue ,
} ;
2022-11-22 19:52:13 +01:00
let location = pointer . current_location ( ) - output . current_location ( ) . to_f64 ( ) ;
2022-08-05 16:28:05 +02:00
2022-11-03 18:51:27 +01:00
if mode ! = CursorMode ::None {
elements . extend (
cursor ::draw_cursor (
renderer ,
seat ,
location ,
scale . into ( ) ,
2022-11-17 20:32:54 +01:00
state . clock . now ( ) ,
2022-11-03 18:51:27 +01:00
mode ! = CursorMode ::NotDefault ,
)
. into_iter ( )
. map ( E ::from ) ,
) ;
}
2022-10-26 15:26:07 +02:00
if let Some ( wl_surface ) = get_dnd_icon ( seat ) {
elements . extend (
2022-11-28 17:48:50 +01:00
cursor ::draw_dnd_icon ( renderer , & wl_surface , location . to_i32_round ( ) , scale )
2022-10-26 15:26:07 +02:00
. into_iter ( )
. map ( E ::from ) ,
) ;
}
if let Some ( grab_elements ) = seat
. user_data ( )
. get ::< SeatMoveGrabState > ( )
. unwrap ( )
. borrow ( )
. as_ref ( )
2022-11-28 17:48:50 +01:00
. map ( | state | state . render ::< E , R > ( renderer , seat , output ) )
2022-10-26 15:26:07 +02:00
{
elements . extend ( grab_elements ) ;
}
2022-08-05 16:28:05 +02:00
}
2022-09-28 12:01:29 +02:00
elements
2022-08-05 16:28:05 +02:00
}
2023-03-07 22:20:44 +01:00
pub fn workspace_elements < R > (
2023-03-07 16:37:11 +01:00
_gpu : Option < & DrmNode > ,
renderer : & mut R ,
state : & mut Common ,
output : & Output ,
2023-05-22 20:19:11 +02:00
previous : Option < ( WorkspaceHandle , usize , Instant ) > ,
current : ( WorkspaceHandle , usize ) ,
2023-03-07 16:37:11 +01:00
cursor_mode : CursorMode ,
_fps : & mut Option < & mut Fps > ,
exclude_workspace_overview : bool ,
2023-03-07 22:20:44 +01:00
) -> Result < Vec < CosmicElement < R > > , RenderError < R > >
2023-03-07 16:37:11 +01:00
where
R : Renderer + ImportAll + ImportMem + AsGlowRenderer ,
< R as Renderer > ::TextureId : Clone + 'static ,
2023-04-18 17:10:21 +02:00
< R as Renderer > ::Error : From < GlesError > ,
2023-03-07 16:37:11 +01:00
CosmicMappedRenderElement < R > : RenderElement < R > ,
2023-05-19 19:44:57 +02:00
WorkspaceRenderElement < R > : RenderElement < R > ,
2023-03-07 16:37:11 +01:00
{
#[ cfg(feature = " debug " ) ]
puffin ::profile_function! ( ) ;
2023-03-07 22:20:44 +01:00
let mut elements = cursor_elements ( renderer , state , output , cursor_mode ) ;
2023-03-07 16:37:11 +01:00
#[ cfg(feature = " debug " ) ]
{
let output_geo = output . geometry ( ) ;
let scale = output . current_scale ( ) . fractional_scale ( ) ;
if let Some ( fps ) = _fps . as_mut ( ) {
let fps_overlay = fps_ui (
_gpu ,
state ,
renderer . glow_renderer_mut ( ) ,
* fps ,
Rectangle ::from_loc_and_size (
( 0 , 0 ) ,
( output_geo . size . w . min ( 400 ) , output_geo . size . h . min ( 800 ) ) ,
) ,
scale ,
)
. map_err ( < R as Renderer > ::Error ::from )
. map_err ( RenderError ::Rendering ) ? ;
elements . push ( fps_overlay . into ( ) ) ;
}
if state . shell . outputs . first ( ) = = Some ( output ) {
if let Some ( profiler_overlay ) = profiler_ui (
state ,
renderer . glow_renderer_mut ( ) ,
Rectangle ::from_loc_and_size ( ( 0 , 0 ) , output_geo . size ) ,
scale ,
)
. map_err ( < R as Renderer > ::Error ::from )
. map_err ( RenderError ::Rendering ) ?
{
elements . push ( profiler_overlay . into ( ) ) ;
}
}
}
2023-05-19 19:44:57 +02:00
let overview = state . shell . overview_mode ( ) ;
2023-07-06 00:03:26 +02:00
let ( resize_mode , resize_indicator ) = state . shell . resize_mode ( ) ;
let resize_indicator = resize_indicator . map ( | indicator | ( resize_mode , indicator ) ) ;
2023-09-08 22:16:39 +02:00
let swap_tree = if let OverviewMode ::Started ( Trigger ::KeyboardSwap ( _ , desc ) , _ ) = & overview . 0 {
if let Some ( desc_output ) = desc . output . upgrade ( ) {
if output ! = & desc_output | | current . 0 ! = desc . handle {
state
. shell
. space_for_handle ( & desc . handle )
. and_then ( | w | w . tiling_layer . tree_for_output ( & desc_output ) )
} else {
None
}
} else {
None
}
} else {
None
} ;
let overview = (
overview . 0 ,
overview . 1. map ( | indicator | ( indicator , swap_tree ) ) ,
) ;
2023-07-06 00:03:26 +02:00
2023-03-07 16:37:11 +01:00
let last_active_seat = state . last_active_seat ( ) . clone ( ) ;
let move_active = last_active_seat
. user_data ( )
. get ::< SeatMoveGrabState > ( )
. unwrap ( )
. borrow ( )
. is_some ( ) ;
2023-03-10 16:20:17 -08:00
let active_output = last_active_seat . active_output ( ) ;
2023-05-22 20:19:11 +02:00
let output_size = output . geometry ( ) . size ;
let output_scale = output . current_scale ( ) . fractional_scale ( ) ;
let workspace = state
. shell
. space_for_handle ( & current . 0 )
. ok_or ( OutputNoMode ) ? ;
2023-09-13 20:14:54 +02:00
let has_fullscreen = workspace . fullscreen . get ( output ) . map ( | f | f . exclusive ) ;
2023-07-13 17:19:29 +02:00
let ( overlay_elements , overlay_popups ) =
split_layer_elements ( renderer , output , Layer ::Overlay , exclude_workspace_overview ) ;
// overlay is above everything
elements . extend ( overlay_popups . into_iter ( ) . map ( Into ::into ) ) ;
elements . extend ( overlay_elements . into_iter ( ) . map ( Into ::into ) ) ;
2023-09-13 20:14:54 +02:00
let mut window_elements = if ! has_fullscreen . unwrap_or ( false ) {
2023-07-13 17:19:29 +02:00
let ( top_elements , top_popups ) =
split_layer_elements ( renderer , output , Layer ::Top , exclude_workspace_overview ) ;
elements . extend ( top_popups . into_iter ( ) . map ( Into ::into ) ) ;
top_elements . into_iter ( ) . map ( Into ::into ) . collect ( )
} else {
Vec ::new ( )
} ;
2023-05-22 20:19:11 +02:00
let offset = match previous . as_ref ( ) {
Some ( ( previous , previous_idx , start ) ) = > {
2023-05-25 00:10:24 +02:00
let layout = state . config . static_conf . workspace_layout ;
2023-05-22 20:19:11 +02:00
let workspace = state
. shell
. space_for_handle ( & previous )
. ok_or ( OutputNoMode ) ? ;
2023-09-13 20:14:54 +02:00
let has_fullscreen = workspace . fullscreen . contains_key ( output ) ;
2023-05-22 20:19:11 +02:00
let is_active_space = workspace . outputs ( ) . any ( | o | o = = & active_output ) ;
let percentage = {
let percentage = Instant ::now ( ) . duration_since ( * start ) . as_millis ( ) as f32
/ ANIMATION_DURATION . as_millis ( ) as f32 ;
2023-07-11 17:12:56 +02:00
ease ( EaseInOutCubic , 0.0 , 1.0 , percentage )
2023-05-22 20:19:11 +02:00
} ;
let offset = Point ::< i32 , Logical > ::from ( match ( layout , * previous_idx < current . 1 ) {
( WorkspaceLayout ::Vertical , true ) = > {
( 0 , ( - output_size . h as f32 * percentage ) . round ( ) as i32 )
}
( WorkspaceLayout ::Vertical , false ) = > {
( 0 , ( output_size . h as f32 * percentage ) . round ( ) as i32 )
}
( WorkspaceLayout ::Horizontal , true ) = > {
( ( - output_size . w as f32 * percentage ) . round ( ) as i32 , 0 )
}
( WorkspaceLayout ::Horizontal , false ) = > {
( ( output_size . w as f32 * percentage ) . round ( ) as i32 , 0 )
}
} ) ;
2023-07-13 17:19:29 +02:00
let ( w_elements , p_elements ) = workspace
. render_output ::< R > (
renderer ,
output ,
& state . shell . override_redirect_windows ,
state . xwayland_state . as_mut ( ) ,
( ! move_active & & is_active_space ) . then_some ( & last_active_seat ) ,
overview . clone ( ) ,
resize_indicator . clone ( ) ,
state . config . static_conf . active_hint ,
)
. map_err ( | _ | OutputNoMode ) ? ;
elements . extend ( p_elements . into_iter ( ) . map ( | p_element | {
CosmicElement ::Workspace ( RelocateRenderElement ::from_element (
p_element ,
offset . to_physical_precise_round ( output_scale ) ,
Relocate ::Relative ,
) )
} ) ) ;
window_elements . extend ( w_elements . into_iter ( ) . map ( | w_element | {
CosmicElement ::Workspace ( RelocateRenderElement ::from_element (
w_element ,
offset . to_physical_precise_round ( output_scale ) ,
Relocate ::Relative ,
) )
} ) ) ;
2023-05-22 20:19:11 +02:00
2023-09-13 20:14:54 +02:00
if ! has_fullscreen {
let ( w_elements , p_elements ) =
background_layer_elements ( renderer , output , exclude_workspace_overview ) ;
elements . extend ( p_elements . into_iter ( ) . map ( | p_element | {
CosmicElement ::Workspace ( RelocateRenderElement ::from_element (
p_element ,
offset . to_physical_precise_round ( output_scale ) ,
Relocate ::Relative ,
) )
} ) ) ;
window_elements . extend ( w_elements . into_iter ( ) . map ( | w_element | {
CosmicElement ::Workspace ( RelocateRenderElement ::from_element (
w_element ,
offset . to_physical_precise_round ( output_scale ) ,
Relocate ::Relative ,
) )
} ) ) ;
}
2023-05-22 20:19:11 +02:00
Point ::< i32 , Logical > ::from ( match ( layout , * previous_idx < current . 1 ) {
( WorkspaceLayout ::Vertical , true ) = > ( 0 , output_size . h + offset . y ) ,
( WorkspaceLayout ::Vertical , false ) = > ( 0 , - ( output_size . h - offset . y ) ) ,
( WorkspaceLayout ::Horizontal , true ) = > ( output_size . w + offset . x , 0 ) ,
( WorkspaceLayout ::Horizontal , false ) = > ( - ( output_size . w - offset . y ) , 0 ) ,
} )
}
None = > ( 0 , 0 ) . into ( ) ,
} ;
2023-03-10 16:20:17 -08:00
let is_active_space = workspace . outputs ( ) . any ( | o | o = = & active_output ) ;
2023-03-07 16:37:11 +01:00
2023-07-13 17:19:29 +02:00
let ( w_elements , p_elements ) = workspace
. render_output ::< R > (
renderer ,
output ,
& state . shell . override_redirect_windows ,
state . xwayland_state . as_mut ( ) ,
( ! move_active & & is_active_space ) . then_some ( & last_active_seat ) ,
overview ,
resize_indicator ,
state . config . static_conf . active_hint ,
)
. map_err ( | _ | OutputNoMode ) ? ;
elements . extend ( p_elements . into_iter ( ) . map ( | p_element | {
CosmicElement ::Workspace ( RelocateRenderElement ::from_element (
p_element ,
offset . to_physical_precise_round ( output_scale ) ,
Relocate ::Relative ,
) )
} ) ) ;
window_elements . extend ( w_elements . into_iter ( ) . map ( | w_element | {
CosmicElement ::Workspace ( RelocateRenderElement ::from_element (
w_element ,
offset . to_physical_precise_round ( output_scale ) ,
Relocate ::Relative ,
) )
} ) ) ;
2023-09-13 20:14:54 +02:00
if has_fullscreen . is_none ( ) {
let ( w_elements , p_elements ) =
background_layer_elements ( renderer , output , exclude_workspace_overview ) ;
elements . extend ( p_elements . into_iter ( ) . map ( | p_element | {
CosmicElement ::Workspace ( RelocateRenderElement ::from_element (
p_element ,
offset . to_physical_precise_round ( output_scale ) ,
Relocate ::Relative ,
) )
} ) ) ;
window_elements . extend ( w_elements . into_iter ( ) . map ( | w_element | {
CosmicElement ::Workspace ( RelocateRenderElement ::from_element (
w_element ,
offset . to_physical_precise_round ( output_scale ) ,
Relocate ::Relative ,
) )
} ) ) ;
}
2023-07-13 17:19:29 +02:00
elements . extend ( window_elements ) ;
2023-03-07 16:37:11 +01:00
Ok ( elements )
}
2023-07-13 17:19:29 +02:00
pub fn split_layer_elements < R > (
2023-05-22 20:19:11 +02:00
renderer : & mut R ,
output : & Output ,
2023-07-13 17:19:29 +02:00
layer : Layer ,
2023-05-22 20:19:11 +02:00
exclude_workspace_overview : bool ,
2023-07-13 17:19:29 +02:00
) -> (
Vec < WorkspaceRenderElement < R > > ,
Vec < WorkspaceRenderElement < R > > ,
)
2023-05-22 20:19:11 +02:00
where
R : Renderer + ImportAll + ImportMem + AsGlowRenderer ,
< R as Renderer > ::TextureId : Clone + 'static ,
< R as Renderer > ::Error : From < GlesError > ,
CosmicMappedRenderElement < R > : RenderElement < R > ,
WorkspaceRenderElement < R > : RenderElement < R > ,
{
let layer_map = layer_map_for_output ( output ) ;
let output_scale = output . current_scale ( ) . fractional_scale ( ) ;
2023-07-13 17:19:29 +02:00
let mut popup_elements = Vec ::new ( ) ;
let mut layer_elements = Vec ::new ( ) ;
2023-05-22 20:19:11 +02:00
layer_map
2023-07-13 17:19:29 +02:00
. layers_on ( layer )
2023-05-22 20:19:11 +02:00
. rev ( )
. filter ( | s | ! ( exclude_workspace_overview & & s . namespace ( ) = = WORKSPACE_OVERVIEW_NAMESPACE ) )
. filter_map ( | surface | {
layer_map
. layer_geometry ( surface )
. map ( | geo | ( geo . loc , surface ) )
} )
2023-07-13 17:19:29 +02:00
. for_each ( | ( location , surface ) | {
let location = location . to_physical_precise_round ( output_scale ) ;
let surface = surface . wl_surface ( ) ;
let scale = Scale ::from ( output_scale ) ;
popup_elements . extend ( PopupManager ::popups_for_surface ( surface ) . flat_map (
| ( popup , popup_offset ) | {
let offset = ( popup_offset - popup . geometry ( ) . loc )
. to_f64 ( )
. to_physical ( scale )
. to_i32_round ( ) ;
render_elements_from_surface_tree (
renderer ,
popup . wl_surface ( ) ,
location + offset ,
scale ,
1.0 ,
2023-09-13 20:24:11 -07:00
Kind ::Unspecified ,
2023-07-13 17:19:29 +02:00
)
} ,
) ) ;
layer_elements . extend ( render_elements_from_surface_tree (
2023-09-13 20:24:11 -07:00
renderer ,
surface ,
location ,
scale ,
1.0 ,
Kind ::Unspecified ,
2023-07-13 17:19:29 +02:00
) ) ;
} ) ;
( layer_elements , popup_elements )
2023-05-22 20:19:11 +02:00
}
// bottom and background layer surfaces
pub fn background_layer_elements < R > (
renderer : & mut R ,
output : & Output ,
exclude_workspace_overview : bool ,
2023-07-13 17:19:29 +02:00
) -> (
Vec < WorkspaceRenderElement < R > > ,
Vec < WorkspaceRenderElement < R > > ,
)
2023-05-22 20:19:11 +02:00
where
R : Renderer + ImportAll + ImportMem + AsGlowRenderer ,
< R as Renderer > ::TextureId : Clone + 'static ,
< R as Renderer > ::Error : From < GlesError > ,
CosmicMappedRenderElement < R > : RenderElement < R > ,
WorkspaceRenderElement < R > : RenderElement < R > ,
{
2023-07-13 17:19:29 +02:00
let ( mut layer_elements , mut popup_elements ) =
split_layer_elements ( renderer , output , Layer ::Bottom , exclude_workspace_overview ) ;
let more = split_layer_elements (
renderer ,
output ,
Layer ::Background ,
exclude_workspace_overview ,
) ;
layer_elements . extend ( more . 0 ) ;
popup_elements . extend ( more . 1 ) ;
( layer_elements , popup_elements )
2023-05-22 20:19:11 +02:00
}
2023-05-19 19:44:57 +02:00
pub fn render_output < R , Target , OffTarget , Source > (
2022-04-22 15:18:28 +02:00
gpu : Option < & DrmNode > ,
2022-03-16 20:05:24 +01:00
renderer : & mut R ,
2022-11-17 20:32:54 +01:00
target : Target ,
2023-03-31 14:04:47 +02:00
damage_tracker : & mut OutputDamageTracker ,
2022-09-28 12:01:29 +02:00
age : usize ,
2022-02-04 21:04:17 +01:00
state : & mut Common ,
output : & Output ,
2022-11-03 18:51:27 +01:00
cursor_mode : CursorMode ,
screencopy : Option < ( Source , & [ ( ScreencopySession , BufferParams ) ] ) > ,
2022-11-22 18:20:20 +01:00
fps : Option < & mut Fps > ,
2023-06-28 22:20:06 +02:00
) -> Result < RenderOutputResult , RenderError < R > >
2022-08-05 14:28:37 +02:00
where
2022-11-03 18:51:27 +01:00
R : Renderer
+ ImportAll
+ ImportMem
+ ExportMem
+ Bind < Dmabuf >
2022-11-17 20:32:54 +01:00
+ Bind < Target >
+ Offscreen < OffTarget >
+ Blit < Source >
+ AsGlowRenderer ,
2022-08-05 14:28:37 +02:00
< R as Renderer > ::TextureId : Clone + 'static ,
2023-04-18 17:10:21 +02:00
< R as Renderer > ::Error : From < GlesError > ,
2022-11-17 20:32:54 +01:00
CosmicElement < R > : RenderElement < R > ,
2022-11-22 10:28:30 +01:00
CosmicMappedRenderElement < R > : RenderElement < R > ,
2023-05-19 19:44:57 +02:00
WorkspaceRenderElement < R > : RenderElement < R > ,
2022-11-03 18:51:27 +01:00
Source : Clone ,
2022-08-05 14:28:37 +02:00
{
2023-05-22 20:19:11 +02:00
let ( previous_workspace , workspace ) = state . shell . workspaces . active ( output ) ;
let ( previous_idx , idx ) = state . shell . workspaces . active_num ( output ) ;
let previous_workspace = previous_workspace
. zip ( previous_idx )
. map ( | ( ( w , start ) , idx ) | ( w . handle , idx , start ) ) ;
let workspace = ( workspace . handle , idx ) ;
2023-01-23 18:25:01 +01:00
let result = render_workspace (
2022-08-05 14:28:37 +02:00
gpu ,
renderer ,
2022-11-17 20:32:54 +01:00
target ,
2022-09-28 12:01:29 +02:00
damage_tracker ,
2022-08-05 14:28:37 +02:00
age ,
state ,
output ,
2023-05-22 20:19:11 +02:00
previous_workspace ,
workspace ,
2022-11-03 18:51:27 +01:00
cursor_mode ,
screencopy ,
2022-11-17 20:32:54 +01:00
fps ,
2023-02-10 14:32:56 -08:00
false ,
2023-01-23 18:25:01 +01:00
) ;
result
2022-08-05 14:28:37 +02:00
}
2023-05-19 19:44:57 +02:00
pub fn render_workspace < R , Target , OffTarget , Source > (
2022-11-03 18:51:27 +01:00
gpu : Option < & DrmNode > ,
2022-08-05 14:28:37 +02:00
renderer : & mut R ,
2022-11-17 20:32:54 +01:00
target : Target ,
2023-03-31 14:04:47 +02:00
damage_tracker : & mut OutputDamageTracker ,
2022-09-28 12:01:29 +02:00
age : usize ,
2022-08-05 14:28:37 +02:00
state : & mut Common ,
output : & Output ,
2023-05-22 20:19:11 +02:00
previous : Option < ( WorkspaceHandle , usize , Instant ) > ,
current : ( WorkspaceHandle , usize ) ,
2022-11-04 17:34:17 +01:00
mut cursor_mode : CursorMode ,
2022-11-03 18:51:27 +01:00
screencopy : Option < ( Source , & [ ( ScreencopySession , BufferParams ) ] ) > ,
2022-11-22 18:20:20 +01:00
mut fps : Option < & mut Fps > ,
2023-02-10 14:32:56 -08:00
exclude_workspace_overview : bool ,
2023-06-28 22:20:06 +02:00
) -> Result < RenderOutputResult , RenderError < R > >
2022-03-16 20:05:24 +01:00
where
2022-11-03 18:51:27 +01:00
R : Renderer
+ ImportAll
+ ImportMem
+ ExportMem
+ Bind < Dmabuf >
2022-11-17 20:32:54 +01:00
+ Bind < Target >
+ Offscreen < OffTarget >
+ Blit < Source >
+ AsGlowRenderer ,
2022-03-16 20:05:24 +01:00
< R as Renderer > ::TextureId : Clone + 'static ,
2023-04-18 17:10:21 +02:00
< R as Renderer > ::Error : From < GlesError > ,
2022-11-17 20:32:54 +01:00
CosmicElement < R > : RenderElement < R > ,
2022-11-22 10:28:30 +01:00
CosmicMappedRenderElement < R > : RenderElement < R > ,
2023-05-19 19:44:57 +02:00
WorkspaceRenderElement < R > : RenderElement < R > ,
2022-11-17 20:32:54 +01:00
Source : Clone ,
2022-03-16 20:05:24 +01:00
{
2023-03-06 18:50:11 +01:00
#[ cfg(feature = " debug " ) ]
puffin ::profile_function! ( ) ;
2022-08-03 16:34:04 +02:00
if let Some ( ref mut fps ) = fps {
2022-02-04 21:04:17 +01:00
fps . start ( ) ;
2022-11-28 17:48:50 +01:00
#[ cfg(feature = " debug " ) ]
2023-02-27 23:54:28 +01:00
if let Some ( rd ) = fps . rd . as_mut ( ) {
rd . start_frame_capture (
renderer . glow_renderer ( ) . egl_context ( ) . get_context_handle ( ) ,
std ::ptr ::null ( ) ,
) ;
2022-11-28 17:48:50 +01:00
}
2022-02-04 21:04:17 +01:00
}
2022-08-05 14:28:37 +02:00
2022-11-04 17:34:17 +01:00
let screencopy_contains_embedded = screencopy . as_ref ( ) . map_or ( false , | ( _ , sessions ) | {
sessions
. iter ( )
. any ( | ( s , _ ) | s . cursor_mode ( ) = = ScreencopyCursorMode ::Embedded )
} ) ;
// cursor handling without a cursor_plane in this case is horrible.
// because what if some session disagree and/or the backend wants to render with a different mode?
// It seems we would need to render to an offscreen buffer in those cases (and do multiple renders, which messes with damage tracking).
// So for now, we just pick the worst mode (embedded), if any requires it.
//
// Once we move to a cursor_plane, the default framebuffer will never contain a cursor and we can just composite the cursor for each session separately on top (or not).
if screencopy_contains_embedded {
cursor_mode = CursorMode ::All ;
} ;
2023-03-07 16:37:11 +01:00
let elements : Vec < CosmicElement < R > > = workspace_elements (
gpu ,
renderer ,
state ,
output ,
2023-05-22 20:19:11 +02:00
previous ,
current ,
2023-03-07 16:37:11 +01:00
cursor_mode ,
& mut fps ,
exclude_workspace_overview ,
) ? ;
2022-11-18 17:20:52 +01:00
if let Some ( fps ) = fps . as_mut ( ) {
fps . elements ( ) ;
}
2022-11-17 20:32:54 +01:00
renderer . bind ( target ) . map_err ( RenderError ::Rendering ) ? ;
2023-02-24 17:41:52 +01:00
let res = damage_tracker . render_output ( renderer , age , & elements , CLEAR_COLOR ) ;
2022-02-04 21:04:17 +01:00
2022-11-17 20:32:54 +01:00
if let Some ( fps ) = fps . as_mut ( ) {
2022-11-18 17:20:52 +01:00
fps . render ( ) ;
2022-04-22 15:18:28 +02:00
}
2022-05-03 13:37:51 +02:00
2022-11-03 18:51:27 +01:00
if let Some ( ( source , buffers ) ) = screencopy {
if res . is_ok ( ) {
for ( session , params ) in buffers {
2022-11-17 20:32:54 +01:00
match render_session (
2022-11-03 18:51:27 +01:00
gpu . cloned ( ) ,
renderer ,
& session ,
params ,
output . current_transform ( ) ,
2023-03-31 14:04:47 +02:00
| _node , buffer , renderer , dt , age | {
let res = dt . damage_output ( age , & elements ) ? ;
2022-11-03 18:51:27 +01:00
if let ( Some ( ref damage ) , _ ) = & res {
2022-11-17 20:32:54 +01:00
if let Ok ( dmabuf ) = get_dmabuf ( buffer ) {
renderer . bind ( dmabuf ) . map_err ( RenderError ::Rendering ) ? ;
} else {
let size = buffer_dimensions ( buffer ) . unwrap ( ) ;
2023-04-18 17:10:21 +02:00
let format =
with_buffer_contents ( buffer , | _ , _ , data | shm_format_to_fourcc ( data . format ) )
. map_err ( | _ | OutputNoMode ) ? // eh, we have to do some error
. expect ( " We should be able to convert all hardcoded shm screencopy formats " ) ;
2022-11-17 20:32:54 +01:00
let render_buffer = renderer
2023-04-18 17:10:21 +02:00
. create_buffer ( format , size )
2022-11-17 20:32:54 +01:00
. map_err ( RenderError ::Rendering ) ? ;
renderer
. bind ( render_buffer )
. map_err ( RenderError ::Rendering ) ? ;
}
2022-11-03 18:51:27 +01:00
for rect in damage {
renderer
. blit_from ( source . clone ( ) , * rect , * rect , TextureFilter ::Nearest )
. map_err ( RenderError ::Rendering ) ? ;
}
}
2023-06-28 22:20:06 +02:00
Ok ( RenderOutputResult {
damage : res . 0 ,
sync : SyncPoint ::default ( ) ,
states : res . 1 ,
} )
2022-11-03 18:51:27 +01:00
} ,
) {
Ok ( true ) = > { } // success
Ok ( false ) = > state . still_pending ( session . clone ( ) , params . clone ( ) ) ,
Err ( err ) = > {
2023-02-24 17:41:52 +01:00
warn! ( ? err , " Error rendering to screencopy session. " ) ;
2022-11-03 18:51:27 +01:00
session . failed ( FailureReason ::Unspec ) ;
}
}
}
}
2022-11-18 17:20:52 +01:00
if let Some ( fps ) = fps . as_mut ( ) {
fps . screencopy ( ) ;
2023-02-27 23:54:28 +01:00
}
}
#[ cfg(feature = " debug " ) ]
if let Some ( ref mut fps ) = fps {
if let Some ( rd ) = fps . rd . as_mut ( ) {
rd . end_frame_capture (
renderer . glow_renderer ( ) . egl_context ( ) . get_context_handle ( ) ,
std ::ptr ::null ( ) ,
) ;
2022-11-18 17:20:52 +01:00
}
2023-03-06 18:50:11 +01:00
puffin ::GlobalProfiler ::lock ( ) . new_frame ( ) ;
2022-11-03 18:51:27 +01:00
}
2022-09-28 12:01:29 +02:00
res
2022-02-04 21:04:17 +01:00
}