diff --git a/config/config.py b/config/config.py index 0588f97..66d864c 100644 --- a/config/config.py +++ b/config/config.py @@ -1,62 +1,77 @@ import chamfer import sys +import os from enum import Enum,auto -try: - import psutil -except ModuleNotFoundError: - print("No psutil module."); - -try: - from Xlib.keysymdef import latin1,miscellany,xf86 - from Xlib import XK -except ModuleNotFoundError: - print("No Xlib module."); - -try: - import pulsectl -except ModuleNotFoundError: - print("No pulsectl module."); - class ShaderFlag(Enum): FOCUS_NEXT = chamfer.shaderFlag.USER_BIT<<0x0 #Refer to the documentation #https://jaelpark.github.io/chamferwm-docs/bindings.html -#for all the keybinding and how to setup them. +#for all the keybindings and how to setup them. +#TLDR: define the key binding in OnSetupKeys() and the +#behaviour in OnKeyPress(). class Container(chamfer.Container): #setup the container before it's created (dimensions) def OnSetupContainer(self): - self.margin = (0.015,0.015); - self.minSize = (0.3,0.2); - - self.splitArmed = False; + self.margin = (0.015,0.015); #Relative amount of empty space around containers (i.e. gap). Fragment shader covers this area for over-reaching decorations. + self.minSize = (0.3,0.2); #Minimum size of the container, before they start to overlap self.titleBar = chamfer.titleBar.TOP; #options for the title bar location are NONE, LEFT, TOP, RIGHT or BOTTOM self.titleStackOnly = True; #disable to always show title bars #WM_CLASS/TITLE rules to force floating mode / never float if self.wm_class == "matplotlib": + #Example: always float matplotlib graph windows. self.floatingMode = chamfer.floatingMode.ALWAYS; #.NEVER #setup the client before it's created (shaders) def OnSetupClient(self): - #TODO: Panels, docks etc. should be rendered with no decorations. Later, it should be possible to check this by looking at the window type property, not just the class name. try: print("Setting up \"{}\" ({})".format(self.wm_name,self.wm_class),flush=True); except UnicodeDecodeError: - print("UnicodeDecodeError",flush=True); + pass; - if self.wm_class == "Conky": - self.vertexShader = "default_vertex.spv"; - self.geometryShader = "default_geometry.spv"; - self.fragmentShader = "default_fragment.spv"; + #Setup shaders for the newly created client container. + #The stock build includes the following fragment shaders + #build by default: + + #1. frame_fragment.spv: default "demo" decoration with border and rounded corners + #2. frame_fragment_basic.spv: rectangular borders with focus highlight, no bling + #3. frame_fragment_ext.spv: #default rounded corner style for external wms + #4. frame_fragment_ext_basic.spv: #simple compatible style for external wms, only draw shadows + #5. default_fragment.spv: #absolutely no decoration (goes together with default_vertex.spv and + # (default_geometry.spv) + + #All shader builds include the shadows by default. Shadows can be + #removed by disabling the build feature in meson.build. Other features + #such as the border thickness, rounding, and colors can be edited + #in the shader source itself (for now). + + if not chamfer.backend.standaloneCompositor: + #Shaders for the chamferwm. + if self.wm_class == "Conky": + #Example: zero decoration for conky system monitor. + #Add docks and other desktop widgets similarly, + #if needed. + self.vertexShader = "default_vertex.spv"; + self.geometryShader = "default_geometry.spv"; + self.fragmentShader = "default_fragment.spv"; + else: + self.vertexShader = "frame_vertex.spv"; + self.geometryShader = "frame_geometry.spv"; + self.fragmentShader = "frame_fragment.spv"; else: + #Shader for other window managers (standalone + #compositor mode). self.vertexShader = "frame_vertex.spv"; self.geometryShader = "frame_geometry.spv"; - self.fragmentShader = "frame_fragment.spv"; + self.fragmentShader = "frame_fragment_ext.spv"; + + #this is just to restore the defaults when leaving fullscreen (used in OnFullscreen()) + self.defaultShaders = (self.vertexShader,self.geometryShader,self.fragmentShader); #select and assign a parent container def OnParent(self): @@ -76,20 +91,19 @@ def OnCreate(self): try: print("Created client \"{}\" ({})".format(self.wm_name,self.wm_class),flush=True); except UnicodeDecodeError: - print("UnicodeDecodeError",flush=True); + pass self.Focus(); - #called to request fullscreen mode - either by calling SetFullscreen or by client message + #Called to request fullscreen mode - either by calling SetFullscreen or by client message. + #Not used in standalone compositor mode. def OnFullscreen(self, toggle): - #In fullscreen mode, no decorations + #In fullscreen mode, no decorations. if toggle: self.vertexShader = "default_vertex.spv"; self.geometryShader = "default_geometry.spv"; self.fragmentShader = "default_fragment.spv"; else: - self.vertexShader = "frame_vertex.spv"; - self.geometryShader = "frame_geometry.spv"; - self.fragmentShader = "frame_fragment.spv"; + self.vertexShader,self.geometryShader,self.fragmentShader = self.defaultShaders; return True; @@ -146,6 +160,7 @@ def FindParentLayout(self, layout): class Backend(chamfer.Backend): def OnSetupKeys(self, debug): + #Helper function to create key binding IDs def KeyId(keyId): nonlocal self; if hasattr(self,keyId): @@ -428,8 +443,12 @@ def OnKeyPress(self, keyId): parent.ShiftLayout(layout); elif keyId == self.SPLIT_V: - #TODO: add render flags property, bitwise OR them - focus.splitArmed = not focus.splitArmed; + try: + #Indicate that next time a container is created, + #the current focus will be split. + focus.splitArmed = not focus.splitArmed; + except AttributeError: + focus.splitArmed = True; elif keyId == self.FULLSCREEN: print("setting fullscreen",flush=True); @@ -553,7 +572,12 @@ def OnKeyPress(self, keyId): focus.Kill(); elif keyId == self.LAUNCH_TERMINAL: - psutil.Popen(["alacritty"],stdout=None,stderr=None); + for t in [os.getenv("TERMINAL"),"alacritty","kitty","urxvt","rxvt","st","xterm"]: + try: + psutil.Popen([t],stdout=None,stderr=None); + break; + except (TypeError,FileNotFoundError): + pass; elif keyId == self.LAUNCH_BROWSER: psutil.Popen(["firefox"],stdout=None,stderr=None); @@ -616,10 +640,29 @@ def OnRedirectExternal(self, title, className): backend = Backend(); chamfer.BindBackend(backend); +if not backend.standaloneCompositor: + #Import some modules for WM use + try: + import psutil + except ModuleNotFoundError: + print("No psutil module."); + + try: + from Xlib.keysymdef import latin1,miscellany,xf86 + from Xlib import XK + except ModuleNotFoundError: + print("No Xlib module."); + + try: + import pulsectl + except ModuleNotFoundError: + print("No pulsectl module."); + compositor = Compositor(); #compositor.deviceIndex = 0; compositor.fontName = "Monospace"; -compositor.fontSize = 32; +compositor.fontSize = 24; +compositor.enableAnimation = True; chamfer.BindCompositor(compositor); if not backend.standaloneCompositor: @@ -632,11 +675,6 @@ def OnRedirectExternal(self, title, className): #psutil.Popen(["feh","--no-fehbg","--image-bg","black","--bg-center","background.png"]); #---startup programs examples: - #launch pulseaudio if installed - #if not "pulseaudio" in pnames: - # print("starting pulseaudio..."); - # psutil.Popen(["sleep 1.0; pulseaudio --start"],shell=True,stdout=None,stderr=None); - #launch notification system #if not "dunst" in pnames: # print("starting dunst..."); diff --git a/meson.build b/meson.build index dead081..4436362 100644 --- a/meson.build +++ b/meson.build @@ -91,8 +91,10 @@ obj = gen_fragment.process(['shaders/default.hlsl']) custom_target('default_fragment',output:'default_fragment.spv',input:obj,command:invoke_spirv_opt,install:true,install_dir:'.') foreach name,args : { - 'fragment':['-DSTOCK_FRAME_STYLE=1'], - 'fragment_basic':['-DSTOCK_FRAME_STYLE=0'] + 'fragment':['-DSTOCK_FRAME_STYLE=1'], #default "demo" decoration with border and rounded corners + 'fragment_basic':['-DSTOCK_FRAME_STYLE=0'], #rectangular borders, no bling + 'fragment_ext':['-DSTOCK_FRAME_STYLE=1','-DDRAW_BORDER=1','-DENABLE_TITLE=0','-DDRAW_SHADOW=1'], #default rounded corner style for external wms + 'fragment_ext_basic':['-DSTOCK_FRAME_STYLE=0','-DDRAW_BORDER=0','-DENABLE_TITLE=0','-DDRAW_SHADOW=1'] #simple compatible style for external wms, only draw shadows } obj = generator(glslc,output:'@BASENAME@_'+name+'_unopt.spv',arguments:glslc_args_fragment).process(['shaders/frame.hlsl'],extra_args:args) custom_target('frame_'+name,output:'frame_'+name+'.spv',input:obj,command:invoke_spirv_opt,install:true,install_dir:'.') diff --git a/shaders/frame.hlsl b/shaders/frame.hlsl index 6470770..eabe41a 100644 --- a/shaders/frame.hlsl +++ b/shaders/frame.hlsl @@ -64,10 +64,18 @@ void main(point float2 posh[1], inout TriangleStream stream){ #define DRAW_SHADOW 1 //set 1 to draw shadow #endif -const float borderScaling = 1.0f; -const float4 borderColor = float4(0.0f,0.0f,0.0f,1.0f); -//const float borderScaling = 0.75f; -//const float4 borderColor = float4(0.07f,0.07f,0.07f,1.0f); +#ifndef DRAW_BORDER +#define DRAW_BORDER 1 +#endif + +#ifndef ENABLE_TITLE +#define ENABLE_TITLE 1 +#endif + +#define BORDER_RADIUS 0.02f +#define BORDER_THICKNESS 0.008f + +const float4 borderColor = float4(0.07f,0.07f,0.07f,1.0f); const float4 focusColor = float4(1.0f,0.6f,0.33f,1.0f); const float4 titleBackground[2] = {float4(0.4f,0.4f,0.4f,1.0f),float4(0.5,0.5,0.5,1.0f)}; //second value for alternating stack tabs const float4 taskSelectColor = float4(0.957f,0.910f,0.824f,1.0f); @@ -90,7 +98,7 @@ float ChamferMap(float2 p, float2 b, float r){ float4 main(float4 posh : SV_Position, float2 texc : TEXCOORD) : SV_Target{ float2 aspect = float2(1.0f,screen.x/screen.y); - float2 borderScalingScr = borderScaling*screen*aspect; + float2 borderScalingScr = screen*aspect; float2 titlePadAspect = 2.0f*titlePad*aspect; float2 xy0_1 = xy0+min(titlePadAspect,0.0f); @@ -101,13 +109,13 @@ float4 main(float4 posh : SV_Position, float2 texc : TEXCOORD) : SV_Target{ float2 center = 0.5f*(a+b); //center location in pixels float2 d1 = b-a; //d1: pixel extent of the window - float2 q = posh.xy-center; + float2 q = posh.xy-center; //pixel offset from center of the window #if STOCK_FRAME_STYLE == 1 - // ----- frame 1: chamfered (demo frame) ------------ + // ----- frame 1: rounded corners (demo frame) ------------------- - float sr1 = ChamferMap(q,0.5f*d1-0.0130f*borderScalingScr.x,0.0195f*borderScalingScr.x); - if(sr1 > 0.0f){ + float sr1 = ChamferMap(q,0.5f*d1-BORDER_RADIUS*borderScalingScr.x,(BORDER_RADIUS+DRAW_BORDER*BORDER_THICKNESS)*borderScalingScr.x); //with border (thicknes determined here) + if(sr1 > -1.0f){ //shadow region #if DRAW_SHADOW if(stackIndex == 0) //only first in stack casts a shadow @@ -120,31 +128,34 @@ float4 main(float4 posh : SV_Position, float2 texc : TEXCOORD) : SV_Target{ return 0.0f; } } - float br = ChamferMap(q,0.5f*d1-0.0104f*borderScalingScr.x,0.0104f*borderScalingScr.x); - if(br > -0.5f){ +#if DRAW_BORDER + //radius extends from the rectangle + float br1 = ChamferMap(q,0.5f*d1-BORDER_RADIUS*borderScalingScr.x,BORDER_RADIUS*borderScalingScr.x); + if(br1 > -1.0f){ //border region if(flags & FLAGS_FOCUS) //dashed line around focus if((any(posh > center-0.5f*d1 && posh < center+0.5f*d1 && fmod(floor(posh/(0.0130f*borderScalingScr.x)),3.0f) < 0.5f) && - any(posh < center-0.5f*d1-0.0037f*borderScalingScr || posh > center+0.5f*d1+0.0037f*borderScalingScr))) + any(posh < center-0.5f*d1-0.6f*BORDER_THICKNESS*borderScalingScr || posh > center+0.5f*d1+0.6f*BORDER_THICKNESS*borderScalingScr))) //0.0037f return focusColor; if(flags & FLAGS_FOCUS_NEXT) if((any(posh > center-0.5f*d1 && posh < center+0.5f*d1 && fmod(floor(posh/(0.0130f*borderScalingScr.x)),3.0f) < 0.5f) && - any(posh < center-0.5f*d1-0.0037f*borderScalingScr || posh > center+0.5f*d1+0.0037f*borderScalingScr))) + any(posh < center-0.5f*d1-0.6f*BORDER_THICKNESS*borderScalingScr || posh > center+0.5f*d1+0.6f*BORDER_THICKNESS*borderScalingScr))) return taskSelectColor; return borderColor; } +#endif //DRAW_BORDER #else //STOCK_FRAME_STYLE - // ----- frame 0: basic ----------------------------- + // ----- frame 0: basic rectangular ------------------------------ - float sr2 = RectangleMap(q,0.5f*d1-(0.0130f-0.0195f)*borderScalingScr.x); - if(sr2 > 0.0f){ + float sr1 = RectangleMap(q,0.5f*d1+DRAW_BORDER*BORDER_THICKNESS*borderScalingScr.x); + if(sr1 > -1.0f){ //shadow region #if DRAW_SHADOW if(stackIndex == 0) //only first in stack casts a shadow - return float4(0.0f,0.0f,0.0f,0.9f*saturate(1.0f-sr2/(0.0078f*borderScalingScr.x))); + return float4(0.0f,0.0f,0.0f,0.9f*saturate(1.0f-sr1/(0.0078f*borderScalingScr.x))); else{ #else { @@ -153,8 +164,9 @@ float4 main(float4 posh : SV_Position, float2 texc : TEXCOORD) : SV_Target{ return 0.0f; } } - float br = RectangleMap(q,0.5f*d1); - if(br > -0.5f){ +#if DRAW_BORDER + float br1 = RectangleMap(q,0.5f*d1); + if(br1 > -1.0f){ //border region if(flags & FLAGS_FOCUS) return focusColor; @@ -163,9 +175,11 @@ float4 main(float4 posh : SV_Position, float2 texc : TEXCOORD) : SV_Target{ return borderColor; } +#endif //DRAW_BORDER #endif //STOCK_FRAME_STYLE float2 a_content = screen*(0.5f*xy0+0.5f); //top-left corner in pixels, content area +#if ENABLE_TITLE float2 b_content = screen*(0.5f*xy1+0.5f); //bottom-right corner in pixels, content area if(any(posh.xy < a_content) || any(posh.xy > b_content)){ //title region bool1 tb = abs(titlePad.x) < abs(titlePad.y); //check whether the title bar is horizontal or vertical @@ -176,6 +190,7 @@ float4 main(float4 posh : SV_Position, float2 texc : TEXCOORD) : SV_Target{ return focusColor; return titleBackground[stackIndex%2]; } +#endif //content region if(flags & FLAGS_CONTAINER_FOCUS){ float4 c = content.Load(float3(posh.xy-a_content,0)); diff --git a/src/backend.cpp b/src/backend.cpp index 9a755a1..456f5f5 100644 --- a/src/backend.cpp +++ b/src/backend.cpp @@ -371,7 +371,8 @@ void X11Container::Place1(WManager::Container *pOrigParent){ } void X11Container::Stack1(){ - const_cast(pbackend)->StackClients(GetRoot()); + if(!pbackend->standaloneComp) + const_cast(pbackend)->StackClients(GetRoot()); } void X11Container::Fullscreen1(){ @@ -699,6 +700,10 @@ void Default::Start(){ values[0] = 1; xcb_create_window(pcon,XCB_COPY_FROM_PARENT,ewmh_window,pscr->root, -1,-1,1,1,0,XCB_WINDOW_CLASS_INPUT_ONLY,XCB_COPY_FROM_PARENT,XCB_CW_OVERRIDE_REDIRECT,values); + + values[0] = XCB_STACK_MODE_BELOW; + xcb_configure_window(pcon,ewmh_window,XCB_CONFIG_WINDOW_STACK_MODE,values); + xcb_map_window(pcon,ewmh_window); if(standaloneComp){ DebugPrintf(stdout,"Launching in standalone compositor mode.\n"); @@ -779,11 +784,6 @@ void Default::Start(){ xcb_change_window_attributes(pcon,pscr->root,XCB_CW_CURSOR,&cursors[CURSOR_POINTER]); } } - - values[0] = XCB_STACK_MODE_BELOW; - xcb_configure_window(pcon,ewmh_window,XCB_CONFIG_WINDOW_STACK_MODE,values); - - xcb_map_window(pcon,ewmh_window); } void Default::Stop(){ @@ -866,18 +866,41 @@ sint Default::HandleEvent(bool forcePoll){ switch(pevent->response_type & 0x7f){ case XCB_CREATE_NOTIFY:{ xcb_create_notify_event_t *pev = (xcb_create_notify_event_t*)pevent; - //if(pev->override_redirect) - // break; - + WManager::Rectangle rect = {pev->x,pev->y,pev->width,pev->height}; + if(!configCache.empty()) + std::get<2>(configCache.back()) = pev->window; + auto mrect = std::find_if(configCache.begin(),configCache.end(),[&](auto &p)->bool{ return pev->window == std::get<0>(p);//p.first; }); if(mrect == configCache.end()){ configCache.push_back(ConfigCacheElement(pev->window,rect,0)); mrect = configCache.end()-1; - }else std::get<1>(*mrect) = rect; + }else{ //tbh there should not be any + std::get<1>(*mrect) = rect; + std::get<2>(*mrect) = 0; + } + //debug: print current and expected stacking order (standalone comp). + //----------------------------------------- +#if 0 +#define debug_query_tree(m) if(standaloneComp){\ + printf("%s\n",m);\ + xcb_query_tree_cookie_t queryTreeCookie = xcb_query_tree(pcon,pscr->root);\ + xcb_query_tree_reply_t *pqueryTreeReply = xcb_query_tree_reply(pcon,queryTreeCookie,0);\ + xcb_window_t *pchs = xcb_query_tree_children(pqueryTreeReply);\ + printf("E\tC\n");\ + for(uint i = 0, n = xcb_query_tree_children_length(pqueryTreeReply); i < n || i < configCache.size(); ++i){\ + printf("%x\t%x\t->%x\n",i < n?pchs[i]:0xff,i < configCache.size()?std::get<0>(configCache[i]):0xff,\ + i < configCache.size()?std::get<2>(configCache[i]):0xff);\ + }\ + free(pqueryTreeReply);} +#else +#define debug_query_tree(m) {} +#endif + //----------------------------------------- + debug_query_tree("CREATE"); DebugPrintf(stdout,"create %x | %d,%d %ux%u\n",pev->window,pev->x,pev->y,pev->width,pev->height); } @@ -979,7 +1002,6 @@ sint Default::HandleEvent(bool forcePoll){ xcb_configure_window(pcon,pev->window,mask,values); - //TODO: do this and the cache in CONFIGURE_NOTIFY? if(pclient1) pclient1->UpdateTranslation(&std::get<1>(*mrect)); @@ -993,6 +1015,8 @@ sint Default::HandleEvent(bool forcePoll){ break; } + DebugPrintf(stdout,"map request, %x\n",pev->window); + result = 1; //check if window already exists @@ -1213,26 +1237,46 @@ sint Default::HandleEvent(bool forcePoll){ XCB_GRAB_MODE_ASYNC,XCB_GRAB_MODE_ASYNC,pscr->root,XCB_NONE,1,XCB_MOD_MASK_1); //check fullscreen - DebugPrintf(stdout,"map request, %x\n",pev->window); } break; case XCB_CONFIGURE_NOTIFY:{ xcb_configure_notify_event_t *pev = (xcb_configure_notify_event_t*)pevent; - //if(pev->override_redirect) - // break; WManager::Rectangle rect = {pev->x,pev->y,pev->width,pev->height}; + //TODO: this and in the CREATE -> function? auto mrect = std::find_if(configCache.begin(),configCache.end(),[&](auto &p)->bool{ - return pev->window == std::get<0>(p); + return pev->window == std::get<0>(p); //fix the above_sibling reference at the old list location }); - if(mrect == configCache.end()){ - configCache.push_back(ConfigCacheElement(pev->window,rect,pev->above_sibling)); - mrect = configCache.end()-1; + if(standaloneComp){ + //order matters when running on other window managers + //TODO: rotate + if(mrect != configCache.end()){ + auto m = configCache.erase(mrect); + if(m != configCache.begin()) + std::get<2>(*(m-1)) = (m != configCache.end())?std::get<0>(*m):0; + } + auto mabv = std::find_if(configCache.begin(),configCache.end(),[&](auto &p)->bool{ + return std::get<0>(p) == pev->above_sibling; + }); + if(mabv != configCache.end()){ + mrect = configCache.insert(mabv+1,ConfigCacheElement(pev->window,rect,std::get<2>(*mabv))); //insert to new location + std::get<2>(*mabv) = pev->window; //fix the above_sibling reference at the new list location + }else{ + //nothing above this client + configCache.push_back(ConfigCacheElement(pev->window,rect,pev->above_sibling)); + mrect = configCache.end()-1; + } }else{ - std::get<1>(*mrect) = rect; - std::get<2>(*mrect) = pev->above_sibling; + if(mrect == configCache.end()){ + configCache.push_back(ConfigCacheElement(pev->window,rect,pev->above_sibling)); + mrect = configCache.end()-1; + }else{ + std::get<1>(*mrect) = rect; + std::get<2>(*mrect) = pev->above_sibling; + } } + debug_query_tree("CONFIG"); X11Client *pclient1 = FindClient(pev->window,MODE_AUTOMATIC); @@ -1241,18 +1285,25 @@ sint Default::HandleEvent(bool forcePoll){ //handle stacking order if(standaloneComp){ - auto m1 = std::find_if(clientStack.begin(),clientStack.end(),[&](auto &p)->bool{ - return static_cast(p)->window == pev->window; - }); - if(pev->above_sibling != 0){ - auto ma = std::find_if(clientStack.begin(),clientStack.end(),[&](auto &p)->bool{ - return static_cast(p)->window == pev->above_sibling; + clientStack.erase(std::remove(clientStack.begin(),clientStack.end(),pclient1),clientStack.end()); + bool found = false; + + //find the first client ("above sibling") that is actually mapped + for(auto k = std::make_reverse_iterator(mrect); k != configCache.rend(); ++k){ //start from above sibling, go reverse + //TODO: skip mrect + auto m = std::find_if(clientStack.begin(),clientStack.end(),[&](auto &p)->bool{ //todo: rbegin/rend() + return static_cast(p)->window == std::get<2>(*k); + //return pev->above_sibling == std::get<0>(*k); }); - if(ma != clientStack.end()) - if(std::distance(m1,ma) > 0) - std::rotate(m1,m1+1,ma+1); - else std::rotate(ma+1,m1,m1+1); - }else std::rotate(clientStack.begin(),m1,m1+1); + if(m != clientStack.end()){ + clientStack.insert(m+1,pclient1); + found = true; + break; + } + } + + if(!found) //nothing was mapped + clientStack.push_back(pclient1); result = 1; } @@ -1263,17 +1314,17 @@ sint Default::HandleEvent(bool forcePoll){ break; case XCB_MAP_NOTIFY:{ xcb_map_notify_event_t *pev = (xcb_map_notify_event_t*)pevent; - //pev->override_redirect - if(pev->window == ewmh_window) + if(pev->window == ewmh_window && !standaloneComp) break; result = 1; - //check if window already exists + //check if window already exists (already mapped) X11Client *pclient1 = FindClient(pev->window,MODE_UNDEFINED); if(pclient1){ //TODO: check if transient_for is in different root this time pclient1->flags &= ~X11Client::FLAG_UNMAPPING; + DebugPrintf(stdout,"Unmapping flag erased.\n"); break; } @@ -1302,14 +1353,19 @@ sint Default::HandleEvent(bool forcePoll){ xcb_get_geometry_cookie_t geometryCookie = xcb_get_geometry(pcon,pev->window); xcb_get_geometry_reply_t *pgeometryReply = xcb_get_geometry_reply(pcon,geometryCookie,0); if(!pgeometryReply) - break; //happens sometimes on high rate of events + break; //hack: happens sometimes on high rate of events WManager::Rectangle rect = {pgeometryReply->x,pgeometryReply->y,pgeometryReply->width,pgeometryReply->height}; free(pgeometryReply); + if(!configCache.empty()) //ensure that the above_sibling is correctly set during the initial tree query + std::get<2>(configCache.back()) = pev->window; + configCache.push_back(ConfigCacheElement(pev->window,rect,0)); mrect = configCache.end()-1; } + debug_query_tree("MAP"); + WManager::Rectangle *prect = &std::get<1>(*mrect); static WManager::Client dummyClient(0); @@ -1370,15 +1426,29 @@ sint Default::HandleEvent(bool forcePoll){ }else if(ApproveExternal(&wmName,&wmClass)){ //clientStack won't get cleared in standalone compositor mode - if(std::get<2>(*mrect) != 0){ //!= XCB_NONE - auto m = std::find_if(clientStack.begin(),clientStack.end(),[&](auto &p)->bool{ - return static_cast(p)->window == std::get<2>(*mrect); + bool found = false; + + //find the first client ("above sibling") that is actually mapped + for(auto k = std::make_reverse_iterator(mrect); k != configCache.rend(); ++k){ //start from above sibling, go reverse + //TODO: skip mrect + auto m = std::find_if(clientStack.begin(),clientStack.end(),[&](auto &p)->bool{ //todo: rbegin/rend() + return static_cast(p)->window == std::get<2>(*k); + //return std::get<2>(*mrect) == std::get<0>(*k); }); - clientStack.insert(m+1,pclient); - }else clientStack.push_front(pclient); + if(m != clientStack.end()){ + clientStack.insert(m+1,pclient); + found = true; + break; + } + } + + if(!found) //nothing was mapped + clientStack.push_back(pclient); } } + DebugPrintf(stdout,"map notify %x, client: %p \"%s\", \"%s\"\n",pev->window,pclient,wmClass.pstr,wmName.pstr); + for(uint i = 0; i < 2; ++i){ if(propertyReply1[i]) free(propertyReply1[i]); @@ -1386,12 +1456,15 @@ sint Default::HandleEvent(bool forcePoll){ mstrfree(pwmProperty[i]); } - DebugPrintf(stdout,"map notify, %x\n",pev->window); } break; case XCB_UNMAP_NOTIFY:{ xcb_unmap_notify_event_t *pev = (xcb_unmap_notify_event_t*)pevent; + DebugPrintf(stdout,"unmap notify %x",pev->window); + + debug_query_tree("UNMAP"); + auto m = std::find_if(clients.begin(),clients.end(),[&](auto &p)->bool{ return p.first->window == pev->window; }); @@ -1400,8 +1473,9 @@ sint Default::HandleEvent(bool forcePoll){ result = 1; - (*m).first->flags |= X11Client::FLAG_UNMAPPING; + //DebugPrintf(stdout,"unmap notify %x, client: %p\n",pev->window,(*m).first); + (*m).first->flags |= X11Client::FLAG_UNMAPPING; clientStack.erase(std::remove(clientStack.begin(),clientStack.end(),(*m).first),clientStack.end()); if((*m).first->pcontainer->GetRoot() != WManager::Container::ptreeFocus->GetRoot()) @@ -1409,16 +1483,11 @@ sint Default::HandleEvent(bool forcePoll){ unmappingQueue.insert((*m).first); - //printf("rect: %d, %d, %ux%u\nold: %d, %d, %ux%u\n",(*m).first->rect.x,(*m).first->rect.y,(*m).first->rect.w,(*m).first->rect.h, - //(*m).first->oldRect.x,(*m).first->oldRect.y,(*m).first->oldRect.w,(*m).first->oldRect.h); - netClientList.clear(); for(auto &p : clients) netClientList.push_back(p.first->window); xcb_change_property(pcon,XCB_PROP_MODE_REPLACE,pscr->root,ewmh._NET_CLIENT_LIST,XCB_ATOM_WINDOW,32,netClientList.size(),netClientList.data()); - DebugPrintf(stdout,"unmap notify window: %x, client: %p\n",pev->window,(*m).first); - std::iter_swap(m,clients.end()-1); clients.pop_back(); } @@ -1713,17 +1782,38 @@ sint Default::HandleEvent(bool forcePoll){ xcb_destroy_notify_event_t *pev = (xcb_destroy_notify_event_t*)pevent; DebugPrintf(stdout,"destroy notify, %x\n",pev->window); + auto m2 = std::find_if(configCache.begin(),configCache.end(),[&](auto &p)->bool{ + return std::get<0>(p) == pev->window; + }); + if(m2 != configCache.end()){ + if(standaloneComp){ + //preserve the order + auto m = configCache.erase(m2); + if(m != configCache.begin()) + std::get<2>(*(m-1)) = (m != configCache.end())?std::get<0>(*m):0; + }else{ + std::iter_swap(m2,configCache.end()-1); + configCache.pop_back(); + } + } + + debug_query_tree("DESTROY"); + //the removal below may not have been done yet if //the client got unmapped in another workspace. - auto m = std::find_if(clients.begin(),clients.end(),[&](auto &p)->bool{ + auto m1 = std::find_if(clients.begin(),clients.end(),[&](auto &p)->bool{ return p.first->window == pev->window; }); - if(m == clients.end()) + if(m1 == clients.end()) break; - unmappingQueue.insert((*m).first); + //Erase here in case unmap was not notified. StackClients() will also take care of the wm mode erasure. + (*m1).first->flags |= X11Client::FLAG_UNMAPPING; //should not be needed here + clientStack.erase(std::remove(clientStack.begin(),clientStack.end(),(*m1).first),clientStack.end()); - std::iter_swap(m,clients.end()-1); + unmappingQueue.insert((*m1).first); + + std::iter_swap(m1,clients.end()-1); clients.pop_back(); } break; @@ -1745,16 +1835,18 @@ sint Default::HandleEvent(bool forcePoll){ } //Destroy the clients here, after the event queue has been cleared. This is to ensure that no already destroyed client is attempted to be readjusted. - if(unmappingQueue.size() > 0){ + if(!unmappingQueue.empty()){ std::set roots; for(X11Client *pclient : unmappingQueue){ + DebugPrintf(stdout,"Unmap queue: %x\n",pclient->window); roots.insert(pclient->pcontainer->GetRoot()); DestroyClient(pclient); } unmappingQueue.clear(); - for(auto m : roots) - StackClients(m); //just to recreate the clientStack + if(!standaloneComp) + for(auto m : roots) + StackClients(m); //just to recreate the clientStack } if(xcb_connection_has_error(pcon)){ diff --git a/src/compositor.cpp b/src/compositor.cpp index c6f8a1e..c2ace1a 100644 --- a/src/compositor.cpp +++ b/src/compositor.cpp @@ -1565,7 +1565,11 @@ void X11ClientFrame::UpdateContents(const VkCommandBuffer *pcommandBuffer){ xcb_flush(pbackend->pcon); if(pimageReply) free(pimageReply); - else DebugPrintf(stderr,"xcb_shm_get_image_reply() returned null.\n"); + else{ + DebugPrintf(stderr,"xcb_shm_get_image_reply() returned null.\n"); + damageRegions.clear(); + return; + } for(VkRect2D &rect1 : damageRegions){ VkRect2D screenRect; @@ -1588,7 +1592,11 @@ void X11ClientFrame::UpdateContents(const VkCommandBuffer *pcommandBuffer){ xcb_flush(pbackend->pcon); if(pimageReply) free(pimageReply); - else DebugPrintf(stderr,"xcb_shm_get_image_reply() returned null.\n"); + else{ + DebugPrintf(stderr,"xcb_shm_get_image_reply() returned null.\n"); + damageRegions.clear(); + return; + } for(uint y = 0; y < rect1.extent.height; ++y){ uint offsetDst = 4*(rect.w*(y+rect1.offset.y)+rect1.offset.x); @@ -1688,7 +1696,7 @@ void X11ClientFrame::StartComposition1(){ if(pcomp->memoryImportMode == CompositorInterface::IMPORT_MODE_DMABUF){ // - CreateSurface(rect.w,rect.h,SURFACE_DEPTH_24); + CreateSurface(rect.w,rect.h,SURFACE_DEPTH_32); if(!dynamic_cast(ptexture)->Attach(windowPixmap)){ xcb_free_pixmap(pbackend->pcon,windowPixmap); return; //do not show @@ -1733,6 +1741,8 @@ void X11ClientFrame::StartComposition1(){ } } + DebugPrintf(stdout,"StartComposition1() for %x\n",window); + damage = xcb_generate_id(pbackend->pcon); xcb_damage_create(pbackend->pcon,damage,window,XCB_DAMAGE_REPORT_LEVEL_NON_EMPTY); @@ -1766,6 +1776,8 @@ void X11ClientFrame::StopComposition1(){ DestroySurface(); + DebugPrintf(stdout,"StopComposition1() for %x\n",window); + pcomp->AddDamageRegion(this); enabled = false; } @@ -1794,34 +1806,6 @@ X11Background::X11Background(xcb_pixmap_t _pixmap, uint _w, uint _h, const char CreateSurface(w,h,SURFACE_DEPTH_24,true); - /*if(pcomp->memoryImportMode == CompositorInterface::IMPORT_MODE_DMABUF){ - CreateSurface(w,h,24); - dynamic_cast(ptexture)->Attach(pixmap); - - UpdateDescSets(); - - }else{ - uint textureSize = w*h*4; - shmid = shmget(IPC_PRIVATE,(textureSize-1)+pcomp->physicalDevExternalMemoryHostProps.minImportedHostPointerAlignment-(textureSize-1)%pcomp->physicalDevExternalMemoryHostProps.minImportedHostPointerAlignment,IPC_CREAT|0777); - if(shmid == -1){ - DebugPrintf(stderr,"Failed to allocate shared memory.\n"); - return; - } - segment = xcb_generate_id(pcomp11->pbackend->pcon); - xcb_shm_attach(pcomp11->pbackend->pcon,segment,shmid,0); - - pchpixels = (unsigned char*)shmat(shmid,0,0); - - CreateSurface(w,h,24); - - if(pcomp->memoryImportMode == CompositorInterface::IMPORT_MODE_HOST_MEMORY){ - if(!dynamic_cast(ptexture)->Attach(pchpixels)){ - DebugPrintf(stderr,"Failed to import host memory. Disabling feature.\n"); - pcomp->memoryImportMode = CompositorInterface::IMPORT_MODE_CPU_COPY; - }else UpdateDescSets(); //view is recreated every time for the imported buffer - } - }*/ - pcomp->FullDamageRegion(); } @@ -1830,17 +1814,6 @@ X11Background::~X11Background(){ shmdt(pchpixels); shmctl(shmid,IPC_RMID,0); - /*if(pcomp->memoryImportMode == CompositorInterface::IMPORT_MODE_DMABUF) - dynamic_cast(ptexture)->Detach(); - else{ - if(pcomp->memoryImportMode != CompositorInterface::IMPORT_MODE_CPU_COPY) - dynamic_cast(ptexture)->Detach(pcomp->frameTag); - - xcb_shm_detach(pcomp11->pbackend->pcon,segment); - shmdt(pchpixels); - - shmctl(shmid,IPC_RMID,0); - }*/ pcomp->FullDamageRegion(); } @@ -1905,7 +1878,8 @@ void X11Compositor::Start(){ if(!poverlayReply) throw Exception("Unable to get overlay window."); overlay = poverlayReply->overlay_win; - free(poverlayReply); DebugPrintf(stdout,"overlay xid: %u\n",overlay); + free(poverlayReply); + DebugPrintf(stdout,"overlay xid: %x\n",overlay); uint mask = XCB_CW_EVENT_MASK; uint values[1] = {XCB_EVENT_MASK_EXPOSURE}; diff --git a/src/config.cpp b/src/config.cpp index 9bdf5d2..544fd78 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -1222,7 +1222,7 @@ Loader::~Loader(){ Py_Finalize(); } -void Loader::Run(const char *pfilePath, const char *pfileLabel){ +bool Loader::Run(const char *pfilePath, const char *pfileLabel){ FILE *pf = 0; if(!pfilePath){ const char *pconfigPaths[] = { @@ -1248,7 +1248,7 @@ void Loader::Run(const char *pfilePath, const char *pfileLabel){ } if(!pf){ DebugPrintf(stderr,"Unable to find configuration file.\n"); - return; + return false; } try{ PyRun_SimpleFile(pf,pfileLabel); @@ -1256,8 +1256,12 @@ void Loader::Run(const char *pfilePath, const char *pfileLabel){ }catch(boost::python::error_already_set &){ boost::python::handle_exception(); PyErr_Clear(); + fclose(pf); + return false; } fclose(pf); + + return true; } bool Loader::standaloneComp; diff --git a/src/config.h b/src/config.h index f7e27ad..ef86f8f 100644 --- a/src/config.h +++ b/src/config.h @@ -221,7 +221,7 @@ class Loader{ public: Loader(const char *); ~Loader(); - void Run(const char *, const char *); + bool Run(const char *, const char *); //backend static bool standaloneComp; diff --git a/src/main.cpp b/src/main.cpp index a5bdd95..7423660 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -993,7 +993,8 @@ int main(sint argc, const char **pargv){ Config::Loader::unredirOnFullscreen = unredirOnFullscreenOpt.Get(); Config::Loader *pconfigLoader = new Config::Loader(pargv[0]); - pconfigLoader->Run(configPath?configPath.Get().c_str():0,"config.py"); + if(!pconfigLoader->Run(configPath?configPath.Get().c_str():0,"config.py")) + return 1; if(staComp.Get()) Config::BackendInterface::pbackendInt->standaloneComp = true;