Skip to content

Commit

Permalink
Standalone compositor 3. #1
Browse files Browse the repository at this point in the history
  • Loading branch information
jaelpark committed Jan 30, 2023
1 parent 9cbcf19 commit 037e757
Show file tree
Hide file tree
Showing 8 changed files with 291 additions and 165 deletions.
124 changes: 81 additions & 43 deletions config/config.py
Original file line number Diff line number Diff line change
@@ -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):
Expand All @@ -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;

Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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:
Expand All @@ -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...");
Expand Down
6 changes: 4 additions & 2 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -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:'.')
Expand Down
53 changes: 34 additions & 19 deletions shaders/frame.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,18 @@ void main(point float2 posh[1], inout TriangleStream<GS_OUTPUT> 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);
Expand All @@ -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);
Expand All @@ -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
Expand All @@ -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
{
Expand All @@ -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;
Expand All @@ -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
Expand All @@ -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));
Expand Down
Loading

0 comments on commit 037e757

Please sign in to comment.