Swayimg API Improved
- The sai (Japanese: 釵, lit. 'hairpin'; Chinese: 鐵尺, lit. 'iron ruler'…)
[Wikipedia]
- Provides the quality of life improvements and api aesthetics like a hairping
- Is much more powerful and completely replaces the original api like a sword overrules a stick
- All basic features that swayimg should have by default.
- shorter and easier to type when accessing the api
.swisimple way to say a lua package is made for swayimg - like.nvimfor neovim- allows vim-style mappings -
<C-S-Del>,<C-.>… - eventloop system based on neovim lua autocommands - almost everything is listenable
- all variables can now be set and read - no more caching of the last set value
- simpler and efficient, yet offers more features and practicality than the original
- Focus on extensibility and ease of use.
- Custom modes! - exemplary usage of filtering mode:
recording.mp4
- options now accessible as variables:
sai.text.size = sai.text.size*1.1 - forward compatible: original api is still directly forwarded through
saiso all additions are available and any setter/enabler and getter methods will automatically be accessible as variables, even if not documented yet. - common actions as directly mappable functions:
v.map('Right', v.go.next) -- image v.map('k', v.pan.up) v.map('Alt+k', function() v.pan.by(70,70) end)
- eventloop: subscribe to any change in the api and trigger your own events for messaging
- inspired by vim event structure and neovim for registering the hooks in lua
- exifdata loader:
- gallery image lazy-loads metadata -> just like viewer mode
- to load all, run
local list=l.get(); require'exiv2'.load_all(list)
- text layer templates:
- track any api variable:
g.text.topright={'Marked: {sai.imagelist.marked.size}'} - pretty-print exif data:
v.text.topleft={'Exposure: {ExposureTime}'} - dynamic event updates - use eventloop hooks to update the text dynamically:
v.text.topright={ {event='User', pattern='help', function(ev) if not ev or not ev.data then return 'Ready to receive messages' end if type(ev.data) == 'string' then return 'Accepts multiline string:\t' .. ev.data elseif type(ev.data) == 'table' then table.insert(data, 1, 'Accepts lines as a table (keybind list):') return ev.data end end} [100] = 'Surely the message is shorter than 100 lines and won\'t override this' } e.trigger{event='User', match='help', data=U.str_bindlist(sai.mode.input)}
- track any api variable:
- style-agnostic keybinds: use gui-, imv- or vim-style keybinds or any style that's right for
you
-- gui, vim, imv-gui, tripple-ctrl-click g.map({ 'Shift+m', '<S- >', 'Alt-h', 'C-3-LMB' }, function() l.marked.set_current 'toggle' g.go.left() end)
- map shell commands directly with ranger-style file placeholders:
%f:'-quoted current file:v.map('Ctrl-e', 'xdg-open %f')%s/%m:'-quoted marked/selected files:v.map('A-s', 'dragon-drop -x -A %s')%s: fallbacks to current file%m: doesn't execute the command if no files were marked
%: unquoted current (like in 4.x):v.map('', [[bash -c '$(which trash || echo rm) "%"']])
- IPC: expose a Unix socket for external programs to evaluate Lua code in swayimg.
local ipc = require 'sai.lib.ipc' local server = ipc.server('/tmp/swi.sock') -- auto-enabled local client = ipc.client('/tmp/swi.sock') -- auto-enabled print(client:send("return sai.text.size")) --> current font size client.enabled = false server.enabled = false
- easily make temporary changes to anything in the api
- all changes are active only while the custom mode is enabled - see
snippets.two_pane_mode - make variable changes and optinally allow the user to adjust the user to adjust them
- automatic event subscriptions and deletions
- define custom keybinds with automatic help page displaying the keybinds
These are modes that aren't meant to be extended or reused, they are just one singleton instance you can configure.
- custom help mode that lets you see all available keybindings and live-updated settings

- command mode for live-evaluating lua code (example of extending input mode)
- two-pane mode for comparing images (limited by the gallery scaling implementation)
- allows you to input arbitrary text and do whatever you want with it
- multiline text
- text selection
- support for all common gui keyboard shortcuts
- deletion (del prev word Ctrl+BS…)
- jumping around (prev word Ctrl+Left, EOF Ctrl+End)
- selection with Shift of everything for jumping (Shift+Left, Shift+Ctrl+Home)
- clipboard support (select all Ctrl+a, Ctrl+c/v/x)
- live filtering by exif data or any other image info
- tab completion for image properties to filter by
- configurable display options - what to live-update (completion, images, filter list…)
- filtering by multiple metrics and operators
- default config and basic usage (see <./mode/filter.lua> for more details):
local fm = require('sai.mode.filter').new { _location = 'topleft', auto_help = true, -- Public, changeable at any time update_imagelist_on_confirm = true, ---Should imagelist be set to filtered images live_imagelist = true, ---Should imagelist be updated with filtering live_pager = true, ---Should a pager with the filtered files be displayed ---Should a pager with completion for the current tag be visible ---`'i'` for matching with ignored casing tag_completion = true, ---@type false|'i'|true } g.map('/', function() fm.enabled = true end)
-
keep_xxx:- keeps image view size constant (depending on chosen metric) regardless of image resolution
- useful for comparing identical images of different sizes
- you will stay zoomed into the same spot of the image even if the other image is half the resolution
xxxcan be replaced with any of the default scaling names orkeep_size
-
add your own:
table.insert( require('sai.api.viewer').custom_scale_handlers, ---@param self sai.api.viewer function(self, x) if type(x) ~= 'table' or not x.width or not x.height then return end e.subscribe { event = 'ImgChanged', match = 'viewer', callback = function(ev) if self._default_scale ~= x then return true end -- unsubscribe local img = ev.data or error() if x.width >= img.width and x.height >= img.height then return end local xscale = x.width / img.width local yscale = x.height / img.height self.super.set_abs_scale(math.min(xscale, yscale)) end, } return 'real' end ) v.default_scale = { width = 2560, height = 1440 }
- create PR to synchronize order of declaring variables/api to align with source code
- create custom mode to dynamically pick which elements should be in each text corner
A collection of small code snippets that might be often wanted. Or can just serve as an inspiration for your own scripts.
Snippets include:
- loading the current directory when swayimg opened with just 1 image
- printing a status message on every variable change (like it used to be)
- resizing the image with the window if the image is in not zoomed in
- cycling fixed scaling and position modes
- notifying on shell command output
- pretty print tables - replace default tostring() method for better table conversion
- command mode for live-executing lua with command history support
- two-pane mode for viewing images side-by-side
True eventloop used by swayimg internally is still inaccessible. That means we cannot listen for file updates and save image state (like scale, position, etc.) before the image gets changed.
Clone the repo into your swayimg config to sai (not sai.swi!).
git clone https://github.com/litoj/sai.swi ~/.config/swayimg/saiDon't forget to add it to .gitignore, if you version your dotfiles
You can add a keybind to update swayimg:
v.map('Alt+F5', require('sai.snippets').update) -- for just viewer mode
local map = require 'sai.binds' -- for any mode combo
map('a', 'Alt+F5', require('sai.snippets').update)To start using the api you only need to load the main module. However, if you also want to use all
the main APIs as globals, you can also load sai.globals to have easier access to them. The
structure is declared in types.lua
-- ~/.config/swayimg/init.lua
-- makes the api accessible through the `sai` global variable
-- you can also just save it to whatever you want
require 'sai.api.init'
-- or through first-letter globals (except: sai.imagelist -> `l` - not `i`)
require 'sai.api.globals'
-- now you can use all options as variables and make intricate behaviour using eventloop hooksIf you're already using lua_ls you only need to include the original swayimg api definitions from which sai reuses the types:
settings.Lua.workspace.library = {'/usr/share/swayimg/swayimg.lua', '/usr/local/share/swayimg/swayimg.lua'}Do whatever you please.