├── lua └── nxwm │ ├── c │ ├── clib.lua │ ├── xfixlib.lua │ ├── gen_lib.lua │ └── xlib.lua │ ├── x11.lua │ └── init.lua ├── LICENSE ├── TODO.md └── README.md /lua/nxwm/c/clib.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | DO NOT EDIT 3 | This file is autogenerated from gen_lib.lua 4 | To regenerate this file, run `nvim -l gen_lib.lua` 5 | --]] 6 | 7 | local ffi=require"ffi" 8 | ffi.cdef[=[ 9 | struct winsize { unsigned short int ws_row; unsigned short int ws_col; unsigned short int ws_xpixel; unsigned short int ws_ypixel; }; 10 | enum {TIOCGWINSZ=21523}; 11 | extern int ioctl (int __fd, unsigned long int __request, ...) __attribute__ ((__nothrow__ , __leaf__)) ;; 12 | ]=] 13 | return ffi.load"c" --[[@as table]] 14 | -------------------------------------------------------------------------------- /lua/nxwm/c/xfixlib.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | DO NOT EDIT 3 | This file is autogenerated from gen_lib.lua 4 | To regenerate this file, run `nvim -l gen_lib.lua` 5 | --]] 6 | 7 | local ffi=require"ffi" 8 | ffi.cdef[=[ 9 | enum {ShapeInput=2}; 10 | enum {ShapeBounding=0}; 11 | typedef unsigned long XID;; 12 | typedef XID XserverRegion;; 13 | struct _XDisplay; 14 | typedef struct _XDisplay Display;; 15 | typedef struct { short x, y; unsigned short width, height; } XRectangle;; 16 | XserverRegion XFixesCreateRegion (Display *dpy, XRectangle *rectangles, int nrectangles);; 17 | void XFixesDestroyRegion (Display *dpy, XserverRegion region);; 18 | typedef XID Window;; 19 | void XFixesSetWindowShapeRegion (Display *dpy, Window win, int shape_kind, int x_off, int y_off, XserverRegion region);; 20 | void XFixesInvertRegion (Display *dpy, XserverRegion dst, XRectangle *rect, XserverRegion src);; 21 | ]=] 22 | return ffi.load"Xfixes" --[[@as table]] 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 altermo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # BUGS to fix 2 | + In neovim-qt, it just doesn't work 3 | # REFACTORING 4 | + Add type hints 5 | + Only remove x-window-buffer when x-window is closed 6 | + When `:bdelete` is run on the x-window-buffer, then don't delete the buffer, but send a delete-x-window request to the x-window 7 | + When `:bdelete!` is run on the x-window-buffer, then xkill the x-window (or first try sending a request, and if that doesn't work, then xkill the x-window (or prompt the user whether to xkill the x-window)) 8 | # FEATURES 9 | + Display(mirror) the x-window in multiple vim-windows at once. 10 | + Instead of using config to create mappings, create a function which can create mappings. 11 | + Be able to create global mappings. 12 | + Detect terminal padding 13 | + Detect if other window managers are running 14 | + Have an `autostart` option to autostart if no window manager is running 15 | + Statusline systray (or some other way to implement systrays) 16 | + Set minimal size for windows which ask for it (using `winheight`/`winwidth`) 17 | + Create an API for easier scripting 18 | + When x-window is floating, open it in vim-window floating window 19 | + Support mouse grab for vim-windows containing x-windows (for drag/resize (floating)) 20 | + Support fullscreen windows 21 | + Support window borders 22 | + Use customisation for x-window-buffer name 23 | + Live change x-window-buffer name depending on x-window name 24 | + Multi screen support 25 | # META 26 | + Look into recreating the plugin in Wayland 27 | -------------------------------------------------------------------------------- /lua/nxwm/x11.lua: -------------------------------------------------------------------------------- 1 | local bit=require'bit' 2 | local ffi=require'ffi' 3 | local xlib=require'nxwm.c.xlib' 4 | local xfixlib=require'nxwm.c.xfixlib' 5 | local clib=require'nxwm.c.clib' 6 | local M={} 7 | 8 | function M.dpyerr() error('X display not open') end 9 | M.code_to_name={ 10 | [xlib.KeyPress]="KeyPress", 11 | [xlib.KeyRelease]="KeyRelease", 12 | [xlib.ButtonPress]="ButtonPress", 13 | [xlib.ButtonRelease]="ButtonRelease", 14 | [xlib.MotionNotify]="MotionNotify", 15 | [xlib.EnterNotify]="EnterNotify", 16 | [xlib.LeaveNotify]="LeaveNotify", 17 | [xlib.FocusIn]="FocusIn", 18 | [xlib.FocusOut]="FocusOut", 19 | [xlib.KeymapNotify]="KeymapNotify", 20 | [xlib.Expose]="Expose", 21 | [xlib.GraphicsExpose]="GraphicsExpose", 22 | [xlib.NoExpose]="NoExpose", 23 | [xlib.VisibilityNotify]="VisibilityNotify", 24 | [xlib.CreateNotify]="CreateNotify", 25 | [xlib.DestroyNotify]="DestroyNotify", 26 | [xlib.UnmapNotify]="UnmapNotify", 27 | [xlib.MapNotify]="MapNotify", 28 | [xlib.MapRequest]="MapRequest", 29 | [xlib.ReparentNotify]="ReparentNotify", 30 | [xlib.ConfigureNotify]="ConfigureNotify", 31 | [xlib.ConfigureRequest]="ConfigureRequest", 32 | [xlib.GravityNotify]="GravityNotify", 33 | [xlib.ResizeRequest]="ResizeRequest", 34 | [xlib.CirculateNotify]="CirculateNotify", 35 | [xlib.CirculateRequest]="CirculateRequest", 36 | [xlib.PropertyNotify]="PropertyNotify", 37 | [xlib.SelectionClear]="SelectionClear", 38 | [xlib.SelectionRequest]="SelectionRequest", 39 | [xlib.SelectionNotify]="SelectionNotify", 40 | [xlib.ColormapNotify]="ColormapNotify", 41 | [xlib.ClientMessage]="ClientMessage", 42 | [xlib.MappingNotify]="MappingNotify", 43 | [xlib.GenericEvent]="GenericEvent", 44 | [xlib.LASTEvent]="LASTEvent", 45 | } 46 | ---@help: https://wiki.archlinux.org/title/Xmodmap#Modifier_keys 47 | M.mod_to_number={ 48 | shift=xlib.ShiftMask, 49 | lock=xlib.LockMask, --Caps_Lock 50 | control=xlib.ControlMask, 51 | mod1=xlib.Mod1Mask, --Alt/Meta 52 | mod2=xlib.Mod2Mask, --Num_Lock 53 | mod3=xlib.Mod3Mask, 54 | mod4=xlib.Mod4Mask, --Super/Hyper 55 | mod5=xlib.Mod5Mask, --ISO_Level3_Shift(AltGr),Mode_switch 56 | } 57 | function M.screen_get_size() 58 | local screen=xlib.XDefaultScreenOfDisplay(M.display) 59 | return screen[0].width,screen[0].height 60 | end 61 | 62 | function M.term_get_info() 63 | local sz=ffi.new'struct winsize[1]' 64 | clib.ioctl(0,clib.TIOCGWINSZ,sz) 65 | return { 66 | row=sz[0].ws_row, 67 | col=sz[0].ws_col, 68 | xpixel=sz[0].ws_xpixel, 69 | ypixel=sz[0].ws_ypixel, 70 | } 71 | end 72 | function M._term_get_id() 73 | if not M.display then M.dpyerr() end 74 | --Some terminals auto focus {{ 75 | local n=ffi.new'int[1]' 76 | local winptr=ffi.new'Window[1]' 77 | xlib.XGetInputFocus(M.display,winptr,n) 78 | if winptr[0]~=1 and winptr[0]~=M.true_root then return winptr[0] end 79 | --}} 80 | local children=ffi.new'Window*[1]' 81 | local nchildren=ffi.new'unsigned int[1]' 82 | if xlib.XQueryTree(M.display,M.true_root,ffi.new'Window[1]',ffi.new'Window[1]',children,nchildren)==0 then 83 | error'' 84 | end 85 | for i=0,nchildren[0]-1 do 86 | local attr=ffi.new'XWindowAttributes[1]' 87 | xlib.XGetWindowAttributes(M.display,children[0][i],attr) 88 | if attr[0].map_state==xlib.IsViewable then 89 | local ret=children[0][i] 90 | xlib.XFree(children[0]) 91 | return ret 92 | end 93 | end 94 | xlib.XFree(children[0]) 95 | error('term window not found') 96 | end 97 | function M.term_focus() 98 | M.win_focus(M.term_root) 99 | end 100 | 101 | function M.key_get_mods(mod) 102 | mod=type(mod)=='table' and mod or {mod} 103 | local ret=0 104 | for _,v in ipairs(mod) do 105 | ret=bit.bor(ret,M.mod_to_number[v:lower()]) 106 | end 107 | return ret 108 | end 109 | function M.key_get_key(key) 110 | return xlib.XKeysymToKeycode(M.display,xlib.XStringToKeysym(key)) 111 | end 112 | 113 | function M.win_position(win,col,row,width,height) 114 | if not M.display then M.dpyerr() end 115 | xlib.XMoveResizeWindow(M.display,win,col,row,width,height) 116 | end 117 | function M.win_send_del_signal(win) 118 | if not M.display then M.dpyerr() end 119 | local msg=ffi.new'XEvent[1]' 120 | msg[0].xclient.type=xlib.ClientMessage 121 | msg[0].xclient.message_type=xlib.XInternAtom(M.display,"WM_PROTOCOLS",0) 122 | msg[0].xclient.window=win 123 | msg[0].xclient.format=32 124 | msg[0].xclient.data.l[0]=xlib.XInternAtom(M.display,"WM_DELETE_WINDOW",0) 125 | xlib.XSendEvent(M.display,win,0,0,msg) 126 | end 127 | function M.win_focus(win) 128 | if not M.display then M.dpyerr() end 129 | xlib.XSetInputFocus(M.display,win,xlib.RevertToParent,xlib.CurrentTime); 130 | end 131 | function M.win_set_key(win,key,mods) 132 | if not M.display then M.dpyerr() end 133 | xlib.XGrabKey(M.display,M.key_get_key(key),M.key_get_mods(mods),win,0,xlib.GrabModeAsync,xlib.GrabModeAsync) 134 | end 135 | function M.win_map(win) 136 | if not M.display then M.dpyerr() end 137 | xlib.XMapWindow(M.display,win) 138 | end 139 | function M.win_unmap(win) 140 | if not M.display then M.dpyerr() end 141 | xlib.XUnmapWindow(M.display,win) 142 | end 143 | function M.win_grab_all_button(win) 144 | if not M.display then M.dpyerr() end 145 | xlib.XGrabButton( 146 | M.display, 147 | xlib.AnyButton, 148 | xlib.AnyModifier, 149 | win, 150 | 0, 151 | xlib.ButtonPressMask, 152 | xlib.GrabModeSync, 153 | xlib.GrabModeSync, 154 | ffi.new('long',0), 155 | ffi.new('long',0) 156 | ) 157 | end 158 | 159 | function M.hide_regions_in_window(win,regions) 160 | local attr=ffi.new'XWindowAttributes[1]' 161 | xlib.XGetWindowAttributes(M.display,win,attr) 162 | local rect=ffi.new('XRectangle[1]') 163 | rect[0].x=0 164 | rect[0].y=0 165 | rect[0].width=attr[0].width 166 | rect[0].height=attr[0].height 167 | local region2=xfixlib.XFixesCreateRegion(M.display,rect,1) 168 | local rects=ffi.new('XRectangle[?]',#regions) 169 | for i,v in ipairs(regions) do 170 | rects[i-1].x=v[1]-attr[0].x 171 | rects[i-1].y=v[2]-attr[0].y 172 | rects[i-1].width=v[3] 173 | rects[i-1].height=v[4] 174 | end 175 | local region=xfixlib.XFixesCreateRegion(M.display,rects,#regions) 176 | xfixlib.XFixesInvertRegion(M.display,region2,rect,region) 177 | xfixlib.XFixesSetWindowShapeRegion(M.display,win,xfixlib.ShapeInput,0,0,region2) 178 | xfixlib.XFixesSetWindowShapeRegion(M.display,win,xfixlib.ShapeBounding,0,0,region2) 179 | xfixlib.XFixesDestroyRegion(M.display,region) 180 | xfixlib.XFixesDestroyRegion(M.display,region2) 181 | end 182 | 183 | function M.start() 184 | if M.display then return end 185 | local display=xlib.XOpenDisplay(nil) 186 | if display==nil then error('X display open error') end 187 | local root=xlib.XRootWindow(display,xlib.XDefaultScreen(display)) 188 | if root==nil then 189 | xlib.XCloseDisplay(display) 190 | error('X root window error') 191 | end 192 | M.display=display 193 | M.true_root=root 194 | xlib.XSelectInput(M.display,root,bit.bor(xlib.SubstructureRedirectMask,xlib.SubstructureNotifyMask,xlib.StructureNotifyMask)) 195 | xlib.XSync(M.display,0) 196 | M.term_root=M._term_get_id() 197 | end 198 | function M.stop() 199 | if not M.display then return end 200 | xlib.XCloseDisplay(M.display) 201 | M.display=nil 202 | end 203 | 204 | function M.step() 205 | if not M.display then M.dpyerr() end 206 | if xlib.XPending(M.display)==0 then return end 207 | local evptr=ffi.new'XEvent[1]' 208 | xlib.XNextEvent(M.display,evptr) 209 | local ev=evptr[0] 210 | if ev.type==xlib.MapRequest then 211 | return {win=ev.xmaprequest.window,type='map'} 212 | elseif ev.type==xlib.UnmapNotify then 213 | return {win=ev.xunmap.window,type='unmap'} 214 | elseif ev.type==xlib.ConfigureRequest then 215 | ---HACK: this is only here so that specific guis work (like xterm) 216 | ---Will be removed when a proper configuration system is implemented 217 | local cev=ev.xconfigurerequest 218 | local changes=ffi.new'XWindowChanges[1]' 219 | changes[0].x=cev.x 220 | changes[0].y=cev.y 221 | changes[0].width=cev.width 222 | changes[0].height=cev.height 223 | xlib.XConfigureWindow(M.display,cev.window,cev.value_mask,changes) 224 | return 225 | elseif ev.type==xlib.KeyRelease then 226 | return {type='key',mod=ev.xkey.state,key=ev.xkey.keycode,win=ev.xkey.window} 227 | elseif ev.type==xlib.DestroyNotify then 228 | return {type='destroy',win=ev.xdestroywindow.window} 229 | elseif ev.type==xlib.ConfigureNotify then 230 | return {type='resize',win=ev.xconfigure.window,width=ev.xconfigure.width,height=ev.xconfigure.height} 231 | elseif ev.type==xlib.ButtonPress then 232 | xlib.XAllowEvents(M.display,xlib.ReplayPointer,xlib.CurrentTime); 233 | return {type='focus',win=ev.xbutton.window} 234 | else 235 | return {type='other',type_id=ev.type} 236 | end 237 | end 238 | return M 239 | -------------------------------------------------------------------------------- /lua/nxwm/c/gen_lib.lua: -------------------------------------------------------------------------------- 1 | if not vim then error('needs to be run with neovim') end 2 | if not pcall(vim.treesitter.language.inspect,'c') then error('treesitter-c parser not found') end 3 | 4 | local queries={ 5 | type_definition=[[;; query 6 | (type_definition 7 | declarator:[(type_identifier) (primitive_type)] @name) 8 | (pointer_declarator 9 | declarator:[(type_identifier) (primitive_type)] @name) 10 | (type_definition 11 | type: [(type_identifier) (primitive_type) (sized_type_specifier)] @type) 12 | (struct_specifier 13 | !body) @type 14 | (type_definition 15 | (struct_specifier 16 | "struct" 17 | name:(type_identifier) @name (#set! pre "struct") 18 | body:(_) 19 | )) 20 | (type_definition 21 | (union_specifier 22 | "union" 23 | name:(type_identifier) @name (#set! pre "union") 24 | body:(_) 25 | )) 26 | (struct_specifier 27 | body: 28 | (field_declaration_list 29 | (field_declaration 30 | type:[(type_identifier) (primitive_type) (sized_type_specifier)] @type))) 31 | (enumerator 32 | name:(identifier) @type) 33 | (parameter_declaration 34 | type:[(type_identifier) (primitive_type) (sized_type_specifier)] @type) 35 | (union_specifier 36 | body: 37 | (field_declaration_list 38 | (field_declaration 39 | type:[(type_identifier) (primitive_type) (sized_type_specifier)] @type))) 40 | ]], 41 | declaration=[[;; query 42 | (declaration 43 | declarator: 44 | [ 45 | (function_declarator 46 | declarator:(identifier) @name) 47 | (pointer_declarator 48 | (function_declarator 49 | declarator:(identifier) @name)) 50 | (pointer_declarator 51 | (pointer_declarator 52 | (function_declarator 53 | declarator:(identifier) @name))) 54 | (function_declarator 55 | (parenthesized_declarator 56 | (pointer_declarator 57 | (function_declarator 58 | declarator:(identifier) @name)))) 59 | (identifier) @name 60 | ]) 61 | 62 | (declaration 63 | type:[(type_identifier) (primitive_type) (sized_type_specifier)] @type) 64 | (struct_specifier 65 | !body) @type 66 | (parameter_declaration 67 | type:[(type_identifier) (primitive_type) (sized_type_specifier)] @type) 68 | ]], 69 | struct_specifier=[[;; query 70 | (struct_specifier 71 | "struct" 72 | name:(type_identifier) @name (#set! pre "struct")) 73 | (struct_specifier 74 | body: 75 | (field_declaration_list 76 | (field_declaration 77 | type:[(type_identifier) (primitive_type) (sized_type_specifier)] @type))) 78 | ]], 79 | union_specifier=[[;; query 80 | (union_specifier 81 | "union" 82 | name:(type_identifier) @name (#set! pre "union")) 83 | 84 | (union_specifier 85 | body: 86 | (field_declaration_list 87 | (field_declaration 88 | type:[(type_identifier) (primitive_type) (sized_type_specifier)] @type))) 89 | ]] 90 | } 91 | 92 | local function get_symbols(libpath) 93 | local systemlist=vim.fn.systemlist({'gcc','-E',libpath}) 94 | local str=require'string.buffer'.new(#systemlist*5) 95 | local ret={} 96 | for _,line in ipairs(systemlist) do 97 | if vim.startswith(line,'#') then goto continue end 98 | if vim.trim(line)=='' then goto continue end 99 | str:put(vim.trim(line)..' ') 100 | ::continue:: 101 | end 102 | for line in io.lines(libpath) do 103 | for _,pattern in ipairs{ 104 | {'^#define%s+([%w_]+)%s+0x(%x+)',16}, 105 | {'^#define%s+([%w_]+)%s+(%d+)'}, 106 | {'^#define%s+([%w_]+)%s+%((%d)L?<<(%d+)%)'}, 107 | } do 108 | local name,number,expr=string.match(line,pattern[1]) 109 | if name then 110 | ret[name]={const=tonumber(number,pattern[2])*(2^(expr or 0))} 111 | break 112 | end 113 | end 114 | end 115 | local buf=str:tostring() 116 | local parser=vim.treesitter.get_string_parser(buf,'c') 117 | local root=parser:parse()[1]:root() 118 | local text=function (node) 119 | return vim.treesitter.get_node_text(node,buf) 120 | end 121 | local outfile='/tmp/user/out.c' 122 | vim.fn.writefile({},outfile) 123 | for node in root:iter_children() do 124 | local query 125 | if queries[node:type()] then 126 | query=vim.treesitter.query.parse('c',queries[node:type()]) 127 | elseif vim.tbl_contains({';','function_definition'},node:type()) then 128 | goto continue 129 | else 130 | error'' 131 | end 132 | local names={} 133 | local types={} 134 | for id,n,metadata in query:iter_captures(node,buf,0,-1) do 135 | if query.captures[id]=='name' then 136 | table.insert(names,(metadata.pre and metadata.pre..' ' or '')..text(n)) 137 | elseif query.captures[id]=='type' then 138 | table.insert(types,text(n)) 139 | end 140 | end 141 | local name=table.remove(names,#names) 142 | ret[name]={req=types,source=text(node)} 143 | for _,altname in ipairs(names) do 144 | ret[altname]={link=name} 145 | end 146 | ::continue:: 147 | end 148 | return ret 149 | end 150 | local function gen_lib(outfile,libpaths,libname,symbols) 151 | local source=vim.split([=[ 152 | --[[ 153 | DO NOT EDIT 154 | This file is autogenerated from gen_lib.lua 155 | To regenerate this file, run `nvim -l gen_lib.lua` 156 | --]] 157 | ]=],'\n') 158 | table.insert(source,'local ffi=require"ffi"') 159 | table.insert(source,'ffi.cdef[=[') 160 | local symbol={ 161 | int={}, 162 | char={}, 163 | long={}, 164 | short={}, 165 | void={}, 166 | ['unsigned long']={}, 167 | ['unsigned short']={}, 168 | ['unsigned short int']={}, 169 | ['unsigned long int']={}, 170 | ['unsigned int']={}, 171 | ['unsigned char']={}, 172 | ['struct _XGC']={}, 173 | } 174 | for _,lib in ipairs(libpaths) do 175 | symbol=vim.tbl_extend('force',symbol,get_symbols(lib)) 176 | end 177 | local function simplify(i) 178 | local s=assert(symbol[i],i) 179 | if s.const then 180 | table.insert(source,('enum {%s=%s};'):format(i,s.const)) 181 | return 182 | elseif s.done then return end 183 | s.done=true 184 | for _,v in ipairs(s.req or {}) do 185 | simplify(v) 186 | end 187 | if s.source then table.insert(source,s.source..';') end 188 | end 189 | for _,i in ipairs(symbols) do 190 | simplify(i) 191 | end 192 | table.insert(source,']=]') 193 | table.insert(source,('return ffi.load"%s" --[[@as table]]'):format(libname)) 194 | vim.fn.writefile(source,outfile) 195 | end 196 | gen_lib('xlib.lua',{ 197 | '/usr/include/X11/Xlib.h', 198 | '/usr/include/X11/X.h', 199 | '/usr/include/X11/keysymdef.h', 200 | },'X11',{ 201 | 'AnyButton', 202 | 'AnyModifier', 203 | 'ButtonPress', 204 | 'ButtonPressMask', 205 | 'ButtonRelease', 206 | 'CirculateNotify', 207 | 'CirculateRequest', 208 | 'ClientMessage', 209 | 'ColormapNotify', 210 | 'ConfigureNotify', 211 | 'ConfigureRequest', 212 | 'ControlMask', 213 | 'CreateNotify', 214 | 'CurrentTime', 215 | 'DestroyNotify', 216 | 'EnterNotify', 217 | 'Expose', 218 | 'FocusIn', 219 | 'FocusOut', 220 | 'GenericEvent', 221 | 'GrabModeAsync', 222 | 'GrabModeSync', 223 | 'GraphicsExpose', 224 | 'GravityNotify', 225 | 'IsViewable', 226 | 'KeyPress', 227 | 'KeyRelease', 228 | 'KeymapNotify', 229 | 'LASTEvent', 230 | 'LeaveNotify', 231 | 'LockMask', 232 | 'MapNotify', 233 | 'MapRequest', 234 | 'MappingNotify', 235 | 'Mod1Mask', 236 | 'Mod2Mask', 237 | 'Mod3Mask', 238 | 'Mod4Mask', 239 | 'Mod5Mask', 240 | 'MotionNotify', 241 | 'NoExpose', 242 | 'PropertyNotify', 243 | 'ReparentNotify', 244 | 'ReplayPointer', 245 | 'ResizeRequest', 246 | 'RevertToParent', 247 | 'SelectionClear', 248 | 'SelectionNotify', 249 | 'SelectionRequest', 250 | 'ShiftMask', 251 | 'StructureNotifyMask', 252 | 'SubstructureNotifyMask', 253 | 'SubstructureRedirectMask', 254 | 'UnmapNotify', 255 | 'VisibilityNotify', 256 | 'Window', 257 | 'XAllowEvents', 258 | 'XCloseDisplay', 259 | 'XConfigureWindow', 260 | 'XDefaultScreen', 261 | 'XDefaultScreenOfDisplay', 262 | 'XEvent', 263 | 'XFree', 264 | 'XGetInputFocus', 265 | 'XGetWindowAttributes', 266 | 'XGrabButton', 267 | 'XGrabKey', 268 | 'XInternAtom', 269 | 'XKeysymToKeycode', 270 | 'XMapWindow', 271 | 'XMoveResizeWindow', 272 | 'XNextEvent', 273 | 'XOpenDisplay', 274 | 'XPending', 275 | 'XQueryTree', 276 | 'XRectangle', 277 | 'XRootWindow', 278 | 'XSelectInput', 279 | 'XSendEvent', 280 | 'XSetInputFocus', 281 | 'XStringToKeysym', 282 | 'XSync', 283 | 'XUnmapWindow', 284 | 'XWindowAttributes', 285 | 'XWindowChanges', 286 | }) 287 | gen_lib('clib.lua',{ 288 | '/usr/include/sys/ioctl.h', 289 | '/usr/include/asm-generic/ioctls.h', 290 | },'c',{ 291 | 'struct winsize', 292 | 'TIOCGWINSZ', 293 | 'ioctl', 294 | }) 295 | gen_lib('xfixlib.lua',{ 296 | '/usr/include/X11/extensions/Xfixes.h', 297 | '/usr/include/X11/extensions/shapeconst.h', 298 | },'Xfixes',{ 299 | 'ShapeInput', 300 | 'ShapeBounding', 301 | 'XFixesCreateRegion', 302 | 'XFixesDestroyRegion', 303 | 'XFixesSetWindowShapeRegion', 304 | 'XFixesInvertRegion', 305 | }) 306 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NXWM 2 | **N**eovim **X**11 **W**indow **M**anager allows you to use x11 windows as if they were buffers. 3 | When entering a x-window-buffer, you'll need to start insert-mode to focus the x-window (unless some configurations are set to do this automatically). 4 | ## Requirements 5 | + `libx11` 6 | + `libxfixes` (almost always installed if libx11 is installed) 7 | + `glibc` (or most other standard C libraries) 8 | + Terminal supporting `TIOCGWINSZ` 9 | + Recommended terminal is `kitty` 10 | + Run `:lua= require'nxwm'.term_supported()` to check 11 | + NOTE: some terminals may support `TIOCGWINSZ` while still not working (like `neovim-qt`) 12 | ## Installation 13 | Use whichever package manager you like.\ 14 | It is recommended to lock/pin the plugin to one version/branch because of changes. 15 | 16 | - lazy 17 | ```lua 18 | {'altermo/nwm',branch='x11'}, 19 | ``` 20 | - packer 21 | ```lua 22 | use {'altermo/nwm',branch='x11'}, 23 | ``` 24 | 25 | ## Configuration 26 | Using `require("nxwm").setup({})` is **not required**, it is only there if you want to change the default config. 27 | ```lua 28 | { 29 | --What happens when a new x-window is created 30 | on_win_open=function (buf,xwin) 31 | vim.cmd.vsplit() 32 | vim.api.nvim_set_current_buf(buf) 33 | end, 34 | --Configuration to pass to window 35 | --`conf` is global config 36 | on_win_get_conf=function (conf,xwin) return conf end, 37 | --How to handle when multiple windows in the same tabpage has the x-window-buffer open 38 | on_multiple_win_open=function (vwins,buf,xwin) 39 | for k,vwin in ipairs(vwins) do 40 | if k~=1 then 41 | local scratchbuf=vim.api.nvim_create_buf(false,true) 42 | vim.bo[scratchbuf].bufhidden='wipe' 43 | vim.api.nvim_win_set_buf(vwin,scratchbuf) 44 | end 45 | end 46 | end, 47 | --Whether to be more verbose 48 | verbal=false, 49 | --Whether to show float windows above x-windows (depending on z-index) 50 | floatover=true, 51 | --Map to unfocus a window (multiple key mappings is not (yet) supported) 52 | unfocus_map='', 53 | --Create your own mappings 54 | --IMPORTANT: the x-window needs to be focused for such mappings to work 55 | maps={ 56 | --{'',function () vim.cmd'quitall!' end}, 57 | --Or you could also have lhs as a table 58 | --{{mods={'control','mod1'},key='Delete'},function () vim.cmd'quitall!' end}, 59 | }, 60 | --Window-opt: auto focus x-window when entering x-window-buffer 61 | autofocus=false, 62 | --Window-opt: try-delete x-window if no vim-window shows buffer (similar to `bufhidden=wipe`) 63 | delhidden=true, 64 | --Window-opt: when click on x-window, goto that buffer (may not focus x-window) 65 | clickgoto=true, 66 | --Window-opt: offset the window this many x pixels (useful if terminal has padding) 67 | xoffset=0, 68 | --Window-opt: offset the window this many y pixels (useful if terminal has padding) 69 | yoffset=0, 70 | } 71 | ``` 72 | ## Usage 73 | 122 | ### Start 123 | Create an executable file with the following contents (or run directly in bash):\ 124 | (click triangle to expand) 125 | 126 | 127 |
From tty using sx 128 | 129 | Install sx (most distros don't have it as a package so you may need to install from [source](https://github.com/Earnestly/sx)) 130 |
Using alacritty 131 | 132 | ```bash 133 | #!/bin/bash 134 | sx alacritty --config-file /dev/null -e nvim -c 'lua require("nxwm").start()' 135 | ``` 136 |
137 |
Using kitty 138 | 139 | ```bash 140 | #!/bin/bash 141 | sx kitty -c NONE -o placement_strategy=top-left -e nvim -c 'lua require("nxwm").start()' 142 | ``` 143 |
144 |
Using wezterm 145 | 146 | **IMPORTANT:** Running NXWM in Wezterm started with sx sometimes doesn't work 147 | ```bash 148 | #!/bin/bash 149 | sx wezterm -n --config enable_tab_bar=false --config window_padding='{left=0,right=0,top=0,bottom=0}' start nvim -c 'lua require"nxwm".start()' 150 | ``` 151 |
152 | 153 | --- 154 | 155 |
156 |
From wayland window manager using Xwayland 157 | 158 | Install Xwayland (may have the package name `xwayland`, `xorg-xwayland` or `xorg-x11-server-Xwayland`) 159 |
Using alacritty 160 | 161 | ```bash 162 | #!/bin/bash 163 | Xwayland :99 -noreset& 164 | sleep 0.05 # HACK to make alacritty work with Xwayland 165 | env -u WAYLAND_DISPLAY DISPLAY=:99 alacritty --config-file /dev/null -e nvim -c 'lua require("nxwm").start()' 166 | jobs -p | xargs kill 167 | ``` 168 |
169 |
Using kitty 170 | 171 | ```bash 172 | #!/bin/bash 173 | Xwayland :99 -noreset& 174 | env -u WAYLAND_DISPLAY DISPLAY=:99 kitty -c NONE -o placement_strategy=top-left -e nvim -c 'lua require("nxwm").start()' 175 | jobs -p | xargs kill 176 | ``` 177 |
178 |
Using wezterm 179 | 180 | ```bash 181 | #!/bin/bash 182 | Xwayland :99 -noreset& 183 | env -u WAYLAND_DISPLAY DISPLAY=:99 wezterm -n --config enable_tab_bar=false --config window_padding='{left=0,right=0,top=0,bottom=0}' start nvim -c 'lua require"nxwm".start()' 184 | jobs -p | xargs kill 185 | ``` 186 |
187 | 188 | --- 189 | 190 |
191 |
From X11 window manager using Xephyr 192 | 193 | Install Xephyr (may be installed together with `xorg-sever` or have the package name `xorg-server-xephyr`) 194 |
Using alacritty 195 | 196 | ```bash 197 | #!/bin/bash 198 | Xephyr -ac -br -noreset :99& 199 | sleep 0.05 # HACK to make alacritty work with Xephyr 200 | env DISPLAY=:99 alacritty --config-file /dev/null -e nvim -c 'lua require("nxwm").start()' 201 | jobs -p | xargs kill 202 | ``` 203 |
204 |
Using kitty 205 | 206 | ```bash 207 | #!/bin/bash 208 | Xephyr -ac -br -noreset :99& 209 | env DISPLAY=:99 kitty -c NONE -o placement_strategy=top-left -e nvim -c 'lua require("nxwm").start()' 210 | jobs -p | xargs kill 211 | ``` 212 |
213 |
Using wezterm 214 | 215 | ```bash 216 | #!/bin/bash 217 | Xephyr -ac -br -noreset :99& 218 | env DISPLAY=:99 wezterm -n --config enable_tab_bar=false --config window_padding='{left=0,right=0,top=0,bottom=0}' start nvim -c 'lua require"nxwm".start()' 219 | jobs -p | xargs kill 220 | ``` 221 |
222 | 223 | --- 224 | 225 |
226 | 227 | 228 | ### Use 229 | Open up a terminal (with `:term`) and run your wanted GUI. 230 | NOTE: x-windows aren't auto focused by default, so start insert (by pressing `i` or similar) and then you'll focus the window. 231 | To unfocus an x-window, either click into another buffer, or press `alt-F4`(unless the default config has been changed). 232 | ## Q&A 233 | #### Is multiple displays supported? 234 | No, and not likely until something like [neovim#2161](https://github.com/neovim/neovim/issues/2161) is implemented. 235 | #### Will there be a wayland version? 236 | Maybe, though wlroots is 10 times more complicated than X11 and much more unstable (e.g. most fails results in crash). 237 | #### How do I exit a focused x-window? 238 | Press `alt-F4` (I know this is an unusual keymap, if someone has a better idea, please let me know). 239 | #### What are some future plans? 240 | See [TODO.md](./TODO.md) (it may be outdated). 241 | 242 | #### Donate 243 | If you want to donate then you need to find the correct link (hint: No Break Here): 244 | * [10]() [11]() [12]() [13]() [14]() [15]() [16]() [17]() [18]() 245 | * [20]() [21]() [22]() [23]() [24]() [25]() [26]() [27]() [28]() 246 | * [30]() [31]() [32]() [33]() [34]() [35]() [36]() [37]() [38]() 247 | * [40]() [41]() [42]() [43]() [44]() [45]() [46]() [47]() [48]() 248 | * [50]() [51]() [52]() [53]() [54]() [55]() [56]() [57]() [58]() 249 | * [60]() [61]() [62]() [63]() [64]() [65]() [66]() [67]() [68]() 250 | * [70]() [71]() [72]() [73]() [74]() [75]() [76]() [77]() [78]() 251 | * [80]() [81]() [82]() [83](https://www.buymeacoffee.com/altermo) [84]() [85]() [86]() [87]() [88]() 252 | 253 | -------------------------------------------------------------------------------- /lua/nxwm/c/xlib.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | DO NOT EDIT 3 | This file is autogenerated from gen_lib.lua 4 | To regenerate this file, run `nvim -l gen_lib.lua` 5 | --]] 6 | 7 | local ffi=require"ffi" 8 | ffi.cdef[=[ 9 | enum {AnyButton=0}; 10 | enum {AnyModifier=32768}; 11 | enum {ButtonPress=4}; 12 | enum {ButtonPressMask=4}; 13 | enum {ButtonRelease=5}; 14 | enum {CirculateNotify=26}; 15 | enum {CirculateRequest=27}; 16 | enum {ClientMessage=33}; 17 | enum {ColormapNotify=32}; 18 | enum {ConfigureNotify=22}; 19 | enum {ConfigureRequest=23}; 20 | enum {ControlMask=4}; 21 | enum {CreateNotify=16}; 22 | enum {CurrentTime=0}; 23 | enum {DestroyNotify=17}; 24 | enum {EnterNotify=7}; 25 | enum {Expose=12}; 26 | enum {FocusIn=9}; 27 | enum {FocusOut=10}; 28 | enum {GenericEvent=35}; 29 | enum {GrabModeAsync=1}; 30 | enum {GrabModeSync=0}; 31 | enum {GraphicsExpose=13}; 32 | enum {GravityNotify=24}; 33 | enum {IsViewable=2}; 34 | enum {KeyPress=2}; 35 | enum {KeyRelease=3}; 36 | enum {KeymapNotify=11}; 37 | enum {LASTEvent=36}; 38 | enum {LeaveNotify=8}; 39 | enum {LockMask=2}; 40 | enum {MapNotify=19}; 41 | enum {MapRequest=20}; 42 | enum {MappingNotify=34}; 43 | enum {Mod1Mask=8}; 44 | enum {Mod2Mask=16}; 45 | enum {Mod3Mask=32}; 46 | enum {Mod4Mask=64}; 47 | enum {Mod5Mask=128}; 48 | enum {MotionNotify=6}; 49 | enum {NoExpose=14}; 50 | enum {PropertyNotify=28}; 51 | enum {ReparentNotify=21}; 52 | enum {ReplayPointer=2}; 53 | enum {ResizeRequest=25}; 54 | enum {RevertToParent=2}; 55 | enum {SelectionClear=29}; 56 | enum {SelectionNotify=31}; 57 | enum {SelectionRequest=30}; 58 | enum {ShiftMask=1}; 59 | enum {StructureNotifyMask=131072}; 60 | enum {SubstructureNotifyMask=524288}; 61 | enum {SubstructureRedirectMask=1048576}; 62 | enum {UnmapNotify=18}; 63 | enum {VisibilityNotify=15}; 64 | typedef unsigned long XID;; 65 | typedef XID Window;; 66 | struct _XDisplay; 67 | typedef struct _XDisplay Display;; 68 | typedef unsigned long Time;; 69 | extern int XAllowEvents( Display* , int , Time );; 70 | extern int XCloseDisplay( Display* );; 71 | typedef struct { int x, y; int width, height; int border_width; Window sibling; int stack_mode; } XWindowChanges;; 72 | extern int XConfigureWindow( Display* , Window , unsigned int , XWindowChanges* );; 73 | extern int XDefaultScreen( Display* );; 74 | typedef char *XPointer;; 75 | typedef struct _XExtData { int number; struct _XExtData *next; int (*free_private)( struct _XExtData *extension ); XPointer private_data; } XExtData;; 76 | typedef unsigned long VisualID;; 77 | typedef struct { XExtData *ext_data; VisualID visualid; int class; unsigned long red_mask, green_mask, blue_mask; int bits_per_rgb; int map_entries; } Visual;; 78 | typedef struct { int depth; int nvisuals; Visual *visuals; } Depth;; 79 | typedef struct _XGC *GC;; 80 | typedef XID Colormap;; 81 | typedef struct { XExtData *ext_data; struct _XDisplay *display; Window root; int width, height; int mwidth, mheight; int ndepths; Depth *depths; int root_depth; Visual *root_visual; GC default_gc; Colormap cmap; unsigned long white_pixel; unsigned long black_pixel; int max_maps, min_maps; int backing_store; int save_unders; long root_input_mask; } Screen;; 82 | extern Screen *XDefaultScreenOfDisplay( Display* );; 83 | typedef struct { int type; unsigned long serial; int send_event; Display *display; Window window; } XAnyEvent;; 84 | typedef struct { int type; unsigned long serial; int send_event; Display *display; Window window; Window root; Window subwindow; Time time; int x, y; int x_root, y_root; unsigned int state; unsigned int keycode; int same_screen; } XKeyEvent;; 85 | typedef struct { int type; unsigned long serial; int send_event; Display *display; Window window; Window root; Window subwindow; Time time; int x, y; int x_root, y_root; unsigned int state; unsigned int button; int same_screen; } XButtonEvent;; 86 | typedef struct { int type; unsigned long serial; int send_event; Display *display; Window window; Window root; Window subwindow; Time time; int x, y; int x_root, y_root; unsigned int state; char is_hint; int same_screen; } XMotionEvent;; 87 | typedef struct { int type; unsigned long serial; int send_event; Display *display; Window window; Window root; Window subwindow; Time time; int x, y; int x_root, y_root; int mode; int detail; int same_screen; int focus; unsigned int state; } XCrossingEvent;; 88 | typedef struct { int type; unsigned long serial; int send_event; Display *display; Window window; int mode; int detail; } XFocusChangeEvent;; 89 | typedef struct { int type; unsigned long serial; int send_event; Display *display; Window window; int x, y; int width, height; int count; } XExposeEvent;; 90 | typedef XID Drawable;; 91 | typedef struct { int type; unsigned long serial; int send_event; Display *display; Drawable drawable; int x, y; int width, height; int count; int major_code; int minor_code; } XGraphicsExposeEvent;; 92 | typedef struct { int type; unsigned long serial; int send_event; Display *display; Drawable drawable; int major_code; int minor_code; } XNoExposeEvent;; 93 | typedef struct { int type; unsigned long serial; int send_event; Display *display; Window window; int state; } XVisibilityEvent;; 94 | typedef struct { int type; unsigned long serial; int send_event; Display *display; Window parent; Window window; int x, y; int width, height; int border_width; int override_redirect; } XCreateWindowEvent;; 95 | typedef struct { int type; unsigned long serial; int send_event; Display *display; Window event; Window window; } XDestroyWindowEvent;; 96 | typedef struct { int type; unsigned long serial; int send_event; Display *display; Window event; Window window; int from_configure; } XUnmapEvent;; 97 | typedef struct { int type; unsigned long serial; int send_event; Display *display; Window event; Window window; int override_redirect; } XMapEvent;; 98 | typedef struct { int type; unsigned long serial; int send_event; Display *display; Window parent; Window window; } XMapRequestEvent;; 99 | typedef struct { int type; unsigned long serial; int send_event; Display *display; Window event; Window window; Window parent; int x, y; int override_redirect; } XReparentEvent;; 100 | typedef struct { int type; unsigned long serial; int send_event; Display *display; Window event; Window window; int x, y; int width, height; int border_width; Window above; int override_redirect; } XConfigureEvent;; 101 | typedef struct { int type; unsigned long serial; int send_event; Display *display; Window event; Window window; int x, y; } XGravityEvent;; 102 | typedef struct { int type; unsigned long serial; int send_event; Display *display; Window window; int width, height; } XResizeRequestEvent;; 103 | typedef struct { int type; unsigned long serial; int send_event; Display *display; Window parent; Window window; int x, y; int width, height; int border_width; Window above; int detail; unsigned long value_mask; } XConfigureRequestEvent;; 104 | typedef struct { int type; unsigned long serial; int send_event; Display *display; Window event; Window window; int place; } XCirculateEvent;; 105 | typedef struct { int type; unsigned long serial; int send_event; Display *display; Window parent; Window window; int place; } XCirculateRequestEvent;; 106 | typedef unsigned long Atom;; 107 | typedef struct { int type; unsigned long serial; int send_event; Display *display; Window window; Atom atom; Time time; int state; } XPropertyEvent;; 108 | typedef struct { int type; unsigned long serial; int send_event; Display *display; Window window; Atom selection; Time time; } XSelectionClearEvent;; 109 | typedef struct { int type; unsigned long serial; int send_event; Display *display; Window owner; Window requestor; Atom selection; Atom target; Atom property; Time time; } XSelectionRequestEvent;; 110 | typedef struct { int type; unsigned long serial; int send_event; Display *display; Window requestor; Atom selection; Atom target; Atom property; Time time; } XSelectionEvent;; 111 | typedef struct { int type; unsigned long serial; int send_event; Display *display; Window window; Colormap colormap; int new; int state; } XColormapEvent;; 112 | typedef struct { int type; unsigned long serial; int send_event; Display *display; Window window; Atom message_type; int format; union { char b[20]; short s[10]; long l[5]; } data; } XClientMessageEvent;; 113 | typedef struct { int type; unsigned long serial; int send_event; Display *display; Window window; int request; int first_keycode; int count; } XMappingEvent;; 114 | typedef struct { int type; Display *display; XID resourceid; unsigned long serial; unsigned char error_code; unsigned char request_code; unsigned char minor_code; } XErrorEvent;; 115 | typedef struct { int type; unsigned long serial; int send_event; Display *display; Window window; char key_vector[32]; } XKeymapEvent;; 116 | typedef struct { int type; unsigned long serial; int send_event; Display *display; int extension; int evtype; } XGenericEvent;; 117 | typedef struct { int type; unsigned long serial; int send_event; Display *display; int extension; int evtype; unsigned int cookie; void *data; } XGenericEventCookie;; 118 | typedef union _XEvent { int type; XAnyEvent xany; XKeyEvent xkey; XButtonEvent xbutton; XMotionEvent xmotion; XCrossingEvent xcrossing; XFocusChangeEvent xfocus; XExposeEvent xexpose; XGraphicsExposeEvent xgraphicsexpose; XNoExposeEvent xnoexpose; XVisibilityEvent xvisibility; XCreateWindowEvent xcreatewindow; XDestroyWindowEvent xdestroywindow; XUnmapEvent xunmap; XMapEvent xmap; XMapRequestEvent xmaprequest; XReparentEvent xreparent; XConfigureEvent xconfigure; XGravityEvent xgravity; XResizeRequestEvent xresizerequest; XConfigureRequestEvent xconfigurerequest; XCirculateEvent xcirculate; XCirculateRequestEvent xcirculaterequest; XPropertyEvent xproperty; XSelectionClearEvent xselectionclear; XSelectionRequestEvent xselectionrequest; XSelectionEvent xselection; XColormapEvent xcolormap; XClientMessageEvent xclient; XMappingEvent xmapping; XErrorEvent xerror; XKeymapEvent xkeymap; XGenericEvent xgeneric; XGenericEventCookie xcookie; long pad[24]; } XEvent;; 119 | extern int XFree( void* );; 120 | extern int XGetInputFocus( Display* , Window* , int* );; 121 | typedef struct { int x, y; int width, height; int border_width; int depth; Visual *visual; Window root; int class; int bit_gravity; int win_gravity; int backing_store; unsigned long backing_planes; unsigned long backing_pixel; int save_under; Colormap colormap; int map_installed; int map_state; long all_event_masks; long your_event_mask; long do_not_propagate_mask; int override_redirect; Screen *screen; } XWindowAttributes;; 122 | extern int XGetWindowAttributes( Display* , Window , XWindowAttributes* );; 123 | typedef XID Cursor;; 124 | extern int XGrabButton( Display* , unsigned int , unsigned int , Window , int , unsigned int , int , int , Window , Cursor );; 125 | extern int XGrabKey( Display* , int , unsigned int , Window , int , int , int );; 126 | extern Atom XInternAtom( Display* , const char* , int );; 127 | typedef unsigned char KeyCode;; 128 | typedef XID KeySym;; 129 | extern KeyCode XKeysymToKeycode( Display* , KeySym );; 130 | extern int XMapWindow( Display* , Window );; 131 | extern int XMoveResizeWindow( Display* , Window , int , int , unsigned int , unsigned int );; 132 | extern int XNextEvent( Display* , XEvent* );; 133 | extern Display *XOpenDisplay( const char* );; 134 | extern int XPending( Display* );; 135 | extern int XQueryTree( Display* , Window , Window* , Window* , Window** , unsigned int* );; 136 | typedef struct { short x, y; unsigned short width, height; } XRectangle;; 137 | extern Window XRootWindow( Display* , int );; 138 | extern int XSelectInput( Display* , Window , long );; 139 | extern int XSendEvent( Display* , Window , int , long , XEvent* );; 140 | extern int XSetInputFocus( Display* , Window , int , Time );; 141 | extern KeySym XStringToKeysym( const char* );; 142 | extern int XSync( Display* , int );; 143 | extern int XUnmapWindow( Display* , Window );; 144 | ]=] 145 | return ffi.load"X11" --[[@as table]] 146 | -------------------------------------------------------------------------------- /lua/nxwm/init.lua: -------------------------------------------------------------------------------- 1 | local x11=require'nxwm.x11' 2 | local M={} 3 | M.augroup=vim.api.nvim_create_augroup('nxwm',{}) 4 | ---@type table 5 | M.windows={} 6 | 7 | M.default_config={ 8 | ---@diagnostic disable-next-line: unused-local 9 | on_win_open=function (buf,xwin) 10 | vim.cmd.vsplit() 11 | vim.api.nvim_set_current_buf(buf) 12 | end, 13 | ---@diagnostic disable-next-line: unused-local 14 | on_win_get_conf=function (conf,xwin) return conf end, 15 | ---@diagnostic disable-next-line: unused-local 16 | on_multiple_win_open=function (vwins,buf,xwin) 17 | for k,vwin in ipairs(vwins) do 18 | if k~=1 then 19 | local scratchbuf=vim.api.nvim_create_buf(false,true) 20 | vim.bo[scratchbuf].bufhidden='wipe' 21 | vim.api.nvim_win_set_buf(vwin,scratchbuf) 22 | end 23 | end 24 | end, 25 | verbose=false, 26 | unfocus_map='', 27 | maps={}, 28 | autofocus=false, 29 | delhidden=true, 30 | clickgoto=true, 31 | xoffset=0, 32 | yoffset=0, 33 | floatover=true, 34 | } 35 | M.conf=vim.deepcopy(M.default_config) 36 | function M.setup(conf) 37 | M.conf=vim.tbl_deep_extend('force',M.default_config,conf) 38 | end 39 | 40 | function M.term_supported() 41 | local info=x11.term_get_info() 42 | return info.xpixel~=0 and info.ypixel~=0 43 | end 44 | function M.term_set_size(width,height) 45 | x11.win_position(x11.term_root,0,0,width,height) 46 | end 47 | 48 | function M.win_update_all(event) 49 | if event=='enter' then x11.term_focus() end 50 | for win,_ in pairs(M.windows) do 51 | M.win_update(win,event) 52 | end 53 | if M.conf.floatover then 54 | M.hide_overlayered_windows() 55 | end 56 | end 57 | function M.win_update(hash,event) 58 | hash=type(hash)=='string' and hash or tostring(hash) 59 | local opt=M.windows[hash] 60 | if not opt then return true end 61 | local win=opt.win 62 | if not vim.api.nvim_buf_is_valid(opt.buf) then 63 | M.win_del_win(win) 64 | return 65 | end 66 | if opt.conf.delhidden then 67 | if not vim.fn.win_findbuf(opt.buf)[1] then 68 | M.win_del(win) 69 | return 70 | end 71 | end 72 | local vwins={} 73 | local _repeat=0 74 | while true do 75 | for _,vwin in ipairs(vim.api.nvim_tabpage_list_wins(0)) do 76 | if vim.api.nvim_win_get_buf(vwin)==opt.buf then 77 | table.insert(vwins,vwin) 78 | end 79 | end 80 | if #vwins<=1 then break end 81 | opt.conf.on_multiple_win_open(vwins,opt.buf,win) 82 | vwins={} 83 | _repeat=_repeat+1 84 | if _repeat>100 then error'' end 85 | end 86 | if #vwins==0 then 87 | x11.win_unmap(win) 88 | return 89 | end 90 | local vwin=vwins[1] 91 | x11.win_map(win) 92 | local terminfo=x11.term_get_info() 93 | local xpx=math.floor(terminfo.xpixel/vim.o.columns) 94 | local ypx=math.floor(terminfo.ypixel/vim.o.lines) 95 | local height=vim.api.nvim_win_get_height(vwin) 96 | local width=vim.api.nvim_win_get_width(vwin) 97 | local row,col=unpack(vim.api.nvim_win_get_position(vwin)) 98 | x11.win_position(win,col*xpx+opt.conf.xoffset,row*ypx+opt.conf.yoffset,width*xpx,height*ypx) 99 | if vim.api.nvim_get_current_buf()==opt.buf then 100 | if opt.conf.autofocus and event=='enter' then 101 | opt.focus=true 102 | end 103 | if opt.focus then 104 | vim.cmd.startinsert() 105 | x11.win_focus(win) 106 | else 107 | M.win_unfocus(win) 108 | end 109 | end 110 | end 111 | function M.win_init(win,conf) 112 | if M.windows[tostring(win)] then return end 113 | local opt={win=win,conf=conf.on_win_get_conf(conf,win),focus=false} 114 | opt.buf=vim.api.nvim_create_buf(true,true) 115 | vim.api.nvim_open_term(opt.buf,{}) 116 | M.windows[tostring(win)]=opt 117 | vim.api.nvim_buf_set_name(opt.buf,'nxwm://'..tonumber(win)) 118 | vim.api.nvim_create_autocmd('TermEnter',{callback=function () 119 | opt.focus=true 120 | M.win_update(win) 121 | end,buffer=opt.buf}) 122 | vim.api.nvim_create_autocmd('TermLeave',{callback=function () 123 | opt.focus=false 124 | M.win_update(win) 125 | end,buffer=opt.buf}) 126 | M.win_set_button(win,conf) 127 | M.win_set_keys(win,conf) 128 | conf.on_win_open(opt.buf,win) 129 | end 130 | function M.win_set_keys(win,conf) 131 | for _,map in ipairs({{conf.unfocus_map},unpack(conf.maps)}) do 132 | map=map[1] 133 | if type(map)~='table' then 134 | map=M.key_convert(map) 135 | end 136 | x11.win_set_key(win,map.key,map.mods) 137 | end 138 | end 139 | function M.win_set_button(win,conf) 140 | if not conf.clickgoto then return end 141 | x11.win_grab_all_button(win) 142 | end 143 | function M.win_unfocus(win) 144 | local opt=M.windows[tostring(win)] 145 | opt.focus=false 146 | if vim.api.nvim_get_current_buf()==M.windows[tostring(win)].buf then 147 | if vim.fn.mode()=='t' then 148 | vim.cmd.stopinsert() 149 | end 150 | x11.term_focus() 151 | end 152 | end 153 | function M.win_del(win) 154 | M.win_del_buf(win) 155 | M.win_del_win(win) 156 | end 157 | function M.win_del_buf(win) 158 | if not M.windows[tostring(win)] then return end 159 | local buf=M.windows[tostring(win)].buf 160 | pcall(vim.api.nvim_buf_delete,buf,{force=true}) 161 | M.windows[tostring(win)]=nil 162 | end 163 | function M.win_del_win(win) 164 | x11.win_send_del_signal(win) 165 | end 166 | function M.win_goto(win) 167 | M.win_update_all() 168 | local opt=M.windows[tostring(win)] 169 | if not opt then return end 170 | for _,vwin in ipairs(vim.api.nvim_tabpage_list_wins(0)) do 171 | if vim.api.nvim_win_get_buf(vwin)==opt.buf then 172 | vim.api.nvim_set_current_win(vwin) 173 | break 174 | end 175 | end 176 | end 177 | 178 | function M.hide_overlayered_windows() 179 | local terminfo=x11.term_get_info() 180 | local xpx=math.floor(terminfo.xpixel/vim.o.columns) 181 | local ypx=math.floor(terminfo.ypixel/vim.o.lines) 182 | local regions={} 183 | for _,vwin in ipairs(vim.api.nvim_tabpage_list_wins(0)) do 184 | if vim.api.nvim_win_get_config(vwin).relative~='' and not vim.api.nvim_win_get_config(vwin).hide then 185 | local row,col=unpack(vim.api.nvim_win_get_position(vwin)) 186 | local height=vim.api.nvim_win_get_height(vwin) 187 | local width=vim.api.nvim_win_get_width(vwin) 188 | local zindex=vim.api.nvim_win_get_config(vwin).zindex 189 | table.insert(regions,{col*xpx,row*ypx,width*xpx,height*ypx,zindex}) 190 | end 191 | end 192 | table.sort(regions,function (a,b) return a[5]0 and winfo[2]>=regions[1][5] do 205 | table.remove(regions,1) 206 | if #regions==0 then break end 207 | end 208 | x11.hide_regions_in_window(winfo[1].win,regions) 209 | end 210 | end 211 | 212 | function M.key_handle(win,key,mod) 213 | local conf=M.windows[tostring(win)].conf 214 | local function run(map,callback) 215 | if type(map)~='table' then 216 | map=M.key_convert(map) 217 | end 218 | local key_=map.key 219 | local mod_=map.mods 220 | if mod==x11.key_get_mods(mod_) and key==x11.key_get_key(key_) then 221 | callback() 222 | return true 223 | end 224 | end 225 | if run(conf.unfocus_map,function () 226 | M.win_unfocus(win) 227 | end) then return end 228 | for _,map in ipairs(conf.maps) do 229 | if run(map[1],map[2]) then return end 230 | end 231 | if M.conf.verbose then 232 | vim.notify('key not handled '..key..' '..mod) 233 | end 234 | end 235 | function M.key_convert(keymap) 236 | if vim.api.nvim_strwidth(keymap)~=#keymap then 237 | error('Doesn\'t support utf8 keymap') 238 | end 239 | keymap=vim.fn.keytrans(vim.api.nvim_replace_termcodes(keymap,true,true,true)) 240 | local match 241 | if keymap:sub(1,1)=='<' then 242 | local spec_end=keymap:find('>',1,true) 243 | if not spec_end then 244 | error('Invalid keymap: '..keymap) 245 | elseif #keymap>spec_end then 246 | error('More than one key based keymaps are not supported yet') 247 | end 248 | match={''} 249 | for i in keymap:gmatch('[^<>]') do 250 | if i=='-' and match[#match]~='' then 251 | table.insert(match,'') 252 | else 253 | match[#match]=match[#match]..i 254 | end 255 | end 256 | else 257 | if #keymap>1 then 258 | error('More than one key based keymaps are not supported yet') 259 | end 260 | match={keymap} 261 | end 262 | local function isupper(c) 263 | return #c==1 and c:upper()==c and c:lower()~=c or nil 264 | end 265 | local key=table.remove(match) 266 | local mods={} 267 | for _,mod in ipairs(match) do 268 | mods[mod]=true 269 | end 270 | if not mods.C then mods.S=mods.S or isupper(key) end 271 | local vim_mod_to_mod={ 272 | S='shift', 273 | C='control', 274 | M='mod1', 275 | T='mod1', 276 | D='mod4', 277 | } 278 | local vim_key_to_key={ 279 | [' ']='space',['!']='exclam',['"']='quotedbl',['#']='numbersign', 280 | ['$']='dollar',['%']='percent',['&']='ampersand',["'"]='apostrophe', 281 | ['(']='parenleft',[')']='parenright',['*']='asterisk',['+']='plus', 282 | [',']='comma',['-']='minus',['.']='period',['/']='slash',[':']='colon', 283 | [';']='semicolon',['lt']='less',['=']='equal',['>']='greater', 284 | ['?']='question',['@']='at',['[']='bracketleft', 285 | ['\\']='backslash',['Bslash']='backslash', -- It's but 286 | [']']='bracketright',['^']='asciicircum',['_']='underscore',['`']='grave', 287 | ['{']='braceleft',['Bar']='bar',['}']='braceright',['~']='asciitilde', 288 | BS='BackSpace',NL='Linefeed',CR='Return',Esc='Escape',Space='space', 289 | Del='Delete',PageUp='Page_Up',PageDown='Page_Down', 290 | kUp='KP_Up',kDown='KP_Down',kLeft='KP_Left',kRight='KP_Right', 291 | kHome='KP_Home',kEnd='KP_End',kOrigin='KP_Begin',kPageUp='KP_Page_Up', 292 | kPageDown='KP_Page_Down',kDel='KP_Delete',kPlus='KP_Add',kMinus='KP_Subtract', 293 | kMultiply='KP_Multiply',kDivide='KP_Divide', 294 | kPoint='KP_Decimal', --May be wrong 295 | kComma='KP_Separator',kEqual='KP_Equal',kEnter='KP_Enter', 296 | k0='KP_0',k1='KP_1',k2='KP_2',k3='KP_3',k4='KP_4',k5='KP_5', 297 | k6='KP_6',k7='KP_7',k8='KP_8',k9='KP_9', 298 | } 299 | local ret={mods={},key=assert(vim_key_to_key[key] or #key==1 and key:upper() or key)} 300 | for i in pairs(mods) do 301 | table.insert(ret.mods,vim_mod_to_mod[i]) 302 | end 303 | return ret 304 | end 305 | 306 | function M.step() 307 | local ev=x11.step() 308 | if not ev then return end 309 | if ev.type=='map' then 310 | M.win_init(ev.win,M.conf) 311 | elseif ev.type=='unmap' then 312 | --TODO: close window 313 | elseif ev.type=='key' then 314 | M.key_handle(ev.win,ev.key,ev.mod) 315 | elseif ev.type=='destroy' then 316 | M.win_del_buf(ev.win) 317 | elseif ev.type=='focus' then 318 | M.win_goto(ev.win) 319 | elseif ev.type=='resize' then 320 | if ev.win==x11.true_root then 321 | M.term_set_size(ev.width,ev.height) 322 | end 323 | M.win_update_all() 324 | elseif ev.type=='other' then 325 | if M.conf.verbose then 326 | vim.notify('event not handled '..x11.code_to_name[ev.type_id],vim.log.levels.INFO) 327 | end 328 | else 329 | error'' 330 | end 331 | end 332 | 333 | function M.start() 334 | x11.start() 335 | vim.api.nvim_create_autocmd('VimLeave',{callback=M.stop,group=M.augroup}) 336 | vim.api.nvim_create_autocmd({'WinResized','WinNew','TermOpen','BufDelete','WinClosed'},{callback=function () 337 | vim.schedule(M.win_update_all) 338 | end,group=M.augroup}) 339 | if M.conf.floatover then 340 | local fn 341 | vim.api.nvim_set_decoration_provider(vim.api.nvim_create_namespace('nxwm'),{on_start=function () 342 | if not x11.display then 343 | vim.api.nvim_set_decoration_provider(vim.api.nvim_create_namespace('nxwm'),{}) 344 | return 345 | end 346 | if fn then fn:stop() end 347 | fn=vim.defer_fn(function () 348 | M.hide_overlayered_windows() 349 | end,10) 350 | end}) 351 | end 352 | vim.api.nvim_create_autocmd({'WinEnter','BufWinEnter','TabEnter'},{callback=function () 353 | vim.schedule_wrap(M.win_update_all)('enter') 354 | end,group=M.augroup}) 355 | M.term_set_size(x11.screen_get_size()) 356 | x11.term_focus() 357 | local function t() 358 | if not x11.display then return end 359 | M.step() 360 | vim.defer_fn(t,1) 361 | end 362 | t() 363 | end 364 | function M.stop() 365 | vim.api.nvim_create_augroup('nxwm',{clear=true}) 366 | vim.api.nvim_set_decoration_provider(vim.api.nvim_create_namespace('nxwm'),{}) 367 | x11.stop() 368 | end 369 | return M 370 | --------------------------------------------------------------------------------