Merge pull request #256 from pinnacle-comp/snowcap-lua
Some checks failed
CI (Pinnacle) / Build (push) Waiting to run
CI (Pinnacle) / Run tests (push) Waiting to run
CI (Pinnacle) / Check formatting (push) Waiting to run
CI (Pinnacle) / Clippy check (push) Waiting to run
Build Lua Docs / Build Lua docs (push) Has been cancelled
Build Rust Docs / Build docs (push) Has been cancelled

Preliminary Snowcap integration (Lua)
This commit is contained in:
Ottatop 2024-06-17 18:52:50 -05:00 committed by GitHub
commit aada66ce70
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 812 additions and 36 deletions

View file

@ -35,14 +35,10 @@ Pinnacle is a Wayland compositor built in Rust using [Smithay](https://github.co
It's my attempt at creating something like [AwesomeWM](https://github.com/awesomeWM/awesome) It's my attempt at creating something like [AwesomeWM](https://github.com/awesomeWM/awesome)
for Wayland. for Wayland.
### What is Snowcap? Pinnacle comes integrated with [Snowcap](https://github.com/pinnacle-comp/snowcap), a
You will see references to Snowcap throughout this README. [Snowcap](https://github.com/pinnacle-comp/snowcap) is the very, *very* WIP widget system. Currently it's only being used for the builtin quit prompt and keybind overlay.
very, *very* WIP widget system for Pinnacle. Currently it's only being used for the builtin quit prompt and keybind overlay.
In the future, Snowcap will be used for everything Awesome uses its widget system for: a taskbar, system tray, etc. In the future, Snowcap will be used for everything Awesome uses its widget system for: a taskbar, system tray, etc.
> [!NOTE]
> Only the Rust API has implemented Snowcap integration currently. Lua support soon™
### Features ### Features
- Tag system - Tag system
- Customizable layouts, including most of the ones from Awesome - Customizable layouts, including most of the ones from Awesome
@ -170,16 +166,20 @@ the Lua or Rust default configs standalone, run one of the following in the crat
```sh ```sh
# For a Lua configuration # For a Lua configuration
just install run -- -c "./api/lua/examples/default" just install run -- -c ./api/lua/examples/default
# For a Rust configuration # For a Rust configuration
cargo run -- -c "./api/rust/examples/default_config" cargo run -- -c ./api/rust/examples/default_config
``` ```
When running the default Rust config standalone without compiled Snowcap integration, When running without compiled Snowcap integration,
run the one in the following directory: use the following directories instead:
```sh ```sh
cargo run -- -c "./api/rust/examples/default_config_no_snowcap" # Lua
just install run -- -c ./api/lua/examples/default_no_snowcap
# Rust
cargo run -- -c ./api/rust/examples/default_config_no_snowcap
``` ```
## Custom configuration ## Custom configuration
@ -200,6 +200,8 @@ It will then generate a config at that directory. If Lua is chosen and there are
files in the directory, the generator will prompt to rename them to a backup before continuing. files in the directory, the generator will prompt to rename them to a backup before continuing.
If Rust is chosen, the directory must be manually emptied to continue. If Rust is chosen, the directory must be manually emptied to continue.
Note that this currently copies default configs *with* Snowcap integration.
Run `cargo run -- config gen --help` for information on the command. Run `cargo run -- config gen --help` for information on the command.
## More on configuration ## More on configuration
@ -252,6 +254,8 @@ Rust: https://pinnacle-comp.github.io/rust-reference/main.</b>
> Documentation for other branches can be reached by replacing `main` with the branch you want. > Documentation for other branches can be reached by replacing `main` with the branch you want.
# Controls # Controls
> Yes, ctrl is a bad mod key I know, this will be changed to Awesome keybinds soon
The following are the default controls in the [`default_config`](api/rust/examples/default_config/main.rs). The following are the default controls in the [`default_config`](api/rust/examples/default_config/main.rs).
| Binding | Action | | Binding | Action |
|----------------------------------------------|------------------------------------| |----------------------------------------------|------------------------------------|

View file

@ -1,6 +1,6 @@
{ {
"$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json", "$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json",
"workspace.library": ["./"], "workspace.library": ["./", "../../snowcap/api/lua"],
"runtime.version": "Lua 5.2", "runtime.version": "Lua 5.2",
"--comment": "Format using Stylua instead", "--comment": "Format using Stylua instead",

View file

@ -1,5 +1,10 @@
{ {
"$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json", "$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json",
"workspace.library": ["~/.luarocks/share/lua/5.4/pinnacle.lua", "~/.luarocks/share/lua/5.4/pinnacle"], "workspace.library": [
"~/.luarocks/share/lua/5.4/pinnacle.lua",
"~/.luarocks/share/lua/5.4/pinnacle",
"~/.luarocks/share/lua/5.4/snowcap.lua",
"~/.luarocks/share/lua/5.4/snowcap"
],
"runtime.version": "Lua 5.4", "runtime.version": "Lua 5.4",
} }

View file

@ -7,6 +7,7 @@ require("pinnacle").setup(function(Pinnacle)
local Window = Pinnacle.window local Window = Pinnacle.window
local Layout = Pinnacle.layout local Layout = Pinnacle.layout
local Util = Pinnacle.util local Util = Pinnacle.util
local Snowcap = Pinnacle.snowcap
local key = Input.key local key = Input.key
@ -15,6 +16,13 @@ require("pinnacle").setup(function(Pinnacle)
local terminal = "alacritty" local terminal = "alacritty"
Input.keybind({ mod_key }, "s", function()
Snowcap.integration.keybind_overlay():show()
end, {
group = "Compositor",
description = "Show the keybind overlay",
})
-------------------- --------------------
-- Mousebinds -- -- Mousebinds --
-------------------- --------------------
@ -33,7 +41,7 @@ require("pinnacle").setup(function(Pinnacle)
-- mod_key + alt + q = Quit Pinnacle -- mod_key + alt + q = Quit Pinnacle
Input.keybind({ mod_key, "alt" }, "q", function() Input.keybind({ mod_key, "alt" }, "q", function()
Pinnacle.quit() Snowcap.integration.quit_prompt():show()
end, { end, {
group = "Compositor", group = "Compositor",
description = "Quit Pinnacle", description = "Quit Pinnacle",

View file

@ -0,0 +1,10 @@
{
"$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json",
"workspace.library": [
"~/.luarocks/share/lua/5.4/pinnacle.lua",
"~/.luarocks/share/lua/5.4/pinnacle",
"~/.luarocks/share/lua/5.4/snowcap.lua",
"~/.luarocks/share/lua/5.4/snowcap"
],
"runtime.version": "Lua 5.4",
}

View file

@ -0,0 +1,314 @@
-- neovim users be like
require("pinnacle").setup(function(Pinnacle)
local Input = Pinnacle.input
local Process = Pinnacle.process
local Output = Pinnacle.output
local Tag = Pinnacle.tag
local Window = Pinnacle.window
local Layout = Pinnacle.layout
local Util = Pinnacle.util
local key = Input.key
---@type Modifier
local mod_key = "ctrl"
local terminal = "alacritty"
--------------------
-- Mousebinds --
--------------------
Input.mousebind({ mod_key }, "btn_left", "press", function()
Window.begin_move("btn_left")
end)
Input.mousebind({ mod_key }, "btn_right", "press", function()
Window.begin_resize("btn_right")
end)
--------------------
-- Keybinds --
--------------------
-- mod_key + alt + q = Quit Pinnacle
Input.keybind({ mod_key, "alt" }, "q", function()
Pinnacle.quit()
end, {
group = "Compositor",
description = "Quit Pinnacle",
})
-- mod_key + alt + r = Reload config
Input.keybind({ mod_key, "alt" }, "r", function()
Pinnacle.reload_config()
end, {
group = "Compositor",
description = "Reload the config",
})
-- mod_key + alt + c = Close window
Input.keybind({ mod_key, "alt" }, "c", function()
local focused = Window.get_focused()
if focused then
focused:close()
end
end, {
group = "Window",
description = "Close the focused window",
})
-- mod_key + alt + Return = Spawn `terminal`
Input.keybind({ mod_key }, key.Return, function()
Process.spawn(terminal)
end, {
group = "Process",
description = "Spawn `alacritty`",
})
-- mod_key + alt + space = Toggle floating
Input.keybind({ mod_key, "alt" }, key.space, function()
local focused = Window.get_focused()
if focused then
focused:toggle_floating()
focused:raise()
end
end, {
group = "Window",
description = "Toggle floating on the focused window",
})
-- mod_key + f = Toggle fullscreen
Input.keybind({ mod_key }, "f", function()
local focused = Window.get_focused()
if focused then
focused:toggle_fullscreen()
focused:raise()
end
end, {
group = "Window",
description = "Toggle fullscreen on the focused window",
})
-- mod_key + m = Toggle maximized
Input.keybind({ mod_key }, "m", function()
local focused = Window.get_focused()
if focused then
focused:toggle_maximized()
focused:raise()
end
end, {
group = "Window",
description = "Toggle maximized on the focused window",
})
----------------------
-- Tags and Outputs --
----------------------
local tag_names = { "1", "2", "3", "4", "5" }
-- Setup outputs.
--
-- `Output.setup` allows you to declare things like mode, scale, and tags for outputs.
-- Here we give all outputs tags 1 through 5.
Output.setup({
-- "*" matches all outputs
["*"] = { tags = tag_names },
})
-- If you want to declare output locations as well, you can use `Output.setup_locs`.
-- This will additionally allow you to recalculate output locations on signals like
-- output connect, disconnect, and resize.
--
-- Read the admittedly scuffed docs for more.
-- Tag keybinds
for _, tag_name in ipairs(tag_names) do
-- nil-safety: tags are guaranteed to be on the outputs due to connect_for_all above
-- mod_key + 1-5 = Switch to tags 1-5
Input.keybind({ mod_key }, tag_name, function()
Tag.get(tag_name):switch_to()
end, {
group = "Tag",
description = "Switch to tag " .. tag_name,
})
-- mod_key + shift + 1-5 = Toggle tags 1-5
Input.keybind({ mod_key, "shift" }, tag_name, function()
Tag.get(tag_name):toggle_active()
end, {
group = "Tag",
description = "Toggle tag " .. tag_name,
})
-- mod_key + alt + 1-5 = Move window to tags 1-5
Input.keybind({ mod_key, "alt" }, tag_name, function()
local focused = Window.get_focused()
if focused then
focused:move_to_tag(Tag.get(tag_name) --[[@as TagHandle]])
end
end, {
group = "Tag",
description = "Move the focused window to tag " .. tag_name,
})
-- mod_key + shift + alt + 1-5 = Toggle tags 1-5 on window
Input.keybind({ mod_key, "shift", "alt" }, tag_name, function()
local focused = Window.get_focused()
if focused then
focused:toggle_tag(Tag.get(tag_name) --[[@as TagHandle]])
end
end, {
group = "Tag",
description = "Toggle tag " .. tag_name .. " on the focused window",
})
end
--------------------
-- Layouts --
--------------------
-- Pinnacle does not manage layouts compositor-side.
-- Instead, it delegates computation of layouts to your config,
-- which provides an interface to calculate the size and location of
-- windows that the compositor will use to position windows.
--
-- If you're familiar with River's layout generators, you'll understand the system here
-- a bit better.
--
-- The Lua API provides two layout system abstractions:
-- 1. Layout managers, and
-- 2. Layout generators.
--
-- ### Layout Managers ###
-- A layout manager is a table that contains a `get_active` function
-- that returns some layout generator.
-- A manager is meant to keep track of and choose various layout generators
-- across your usage of the compositor.
--
-- ### Layout generators ###
-- A layout generator is a table that holds some state as well as
-- the `layout` function, which takes in layout arguments and computes
-- an array of geometries that will determine the size and position
-- of windows being laid out.
--
-- There is one built-in layout manager and five built-in layout generators,
-- as shown below.
--
-- Additionally, this system is designed to be user-extensible;
-- you are free to create your own layout managers and generators for
-- maximum customizability! Docs for doing so are in the works, so sit tight.
-- Create a cycling layout manager. This provides methods to cycle
-- between the given layout generators below.
local layout_manager = Layout.new_cycling_manager({
-- `Layout.builtins` contains functions that create various layout generators.
-- Each of these has settings that can be overridden by passing in a table with
-- overriding options.
Layout.builtins.master_stack(),
Layout.builtins.master_stack({ master_side = "right" }),
Layout.builtins.master_stack({ master_side = "top" }),
Layout.builtins.master_stack({ master_side = "bottom" }),
Layout.builtins.dwindle(),
Layout.builtins.spiral(),
Layout.builtins.corner(),
Layout.builtins.corner({ corner_loc = "top_right" }),
Layout.builtins.corner({ corner_loc = "bottom_left" }),
Layout.builtins.corner({ corner_loc = "bottom_right" }),
Layout.builtins.fair(),
Layout.builtins.fair({ direction = "horizontal" }),
})
-- Set the cycling layout manager as the layout manager that will be used.
-- This then allows you to call `Layout.request_layout` to manually layout windows.
Layout.set_manager(layout_manager)
-- mod_key + space = Cycle forward one layout on the focused output
--
-- Yes, this is a bit verbose for my liking.
-- You need to cycle the layout on the first active tag
-- because that is the one that decides which layout is used.
Input.keybind({ mod_key }, key.space, function()
local focused_op = Output.get_focused()
if focused_op then
local tags = focused_op:tags() or {}
local tag = nil
---@type (fun(): (boolean|nil))[]
local tag_actives = {}
for i, t in ipairs(tags) do
tag_actives[i] = function()
return t:active()
end
end
-- We are batching API calls here for better performance
tag_actives = Util.batch(tag_actives)
for i, active in ipairs(tag_actives) do
if active then
tag = tags[i]
break
end
end
if tag then
layout_manager:cycle_layout_forward(tag)
Layout.request_layout(focused_op)
end
end
end, {
group = "Layout",
description = "Cycle the layout forward on the first active tag",
})
-- mod_key + shift + space = Cycle backward one layout on the focused output
Input.keybind({ mod_key, "shift" }, key.space, function()
local focused_op = Output.get_focused()
if focused_op then
local tags = focused_op:tags() or {}
local tag = nil
---@type (fun(): (boolean|nil))[]
local tag_actives = {}
for i, t in ipairs(tags) do
tag_actives[i] = function()
return t:active()
end
end
tag_actives = Util.batch(tag_actives)
for i, active in ipairs(tag_actives) do
if active then
tag = tags[i]
break
end
end
if tag then
layout_manager:cycle_layout_backward(tag)
Layout.request_layout(focused_op)
end
end
end, {
group = "Layout",
description = "Cycle the layout backward on the first active tag",
})
Input.set_libinput_settings({
tap = true,
})
-- Enable sloppy focus
Window.connect_signal({
pointer_enter = function(window)
window:set_focused(true)
end,
})
-- Spawning should happen after you add tags, as Pinnacle currently doesn't render windows without tags.
Process.spawn_once(terminal)
end)

View file

@ -0,0 +1,46 @@
# This metaconfig.toml file dictates what config Pinnacle will run.
#
# When running Pinnacle, the compositor will look in the following directories for a metaconfig.toml file,
# in order from top to bottom:
# $PINNACLE_CONFIG_DIR
# $XDG_CONFIG_HOME/pinnacle/
# ~/.config/pinnacle/
#
# When Pinnacle finds a metaconfig.toml file, it will execute the command provided to `command`.
# To use a Rust config, this should be changed to something like ["cargo", "run"].
#
# Because configuration is done using an external process, if it ever crashes, you lose all of your keybinds.
# The compositor will load the default config if that happens, but in the event that you don't have
# the necessary dependencies for it to run, you may get softlocked.
# In order prevent you from getting stuck in the compositor, you must define keybinds to reload your config
# and kill Pinnacle.
#
# More details on each setting can be found below.
# The command Pinnacle will run on startup and when you reload your config.
# Paths are relative to the directory the metaconfig.toml file is in.
# This must be an array.
command = ["lua", "default_config.lua"]
### Keybinds ###
# Each keybind takes in a table with two fields: `modifiers` and `key`.
# - `modifiers` can be one of "Ctrl", "Alt", "Shift", or "Super".
# - `key` can be a string of any lowercase letter, number,
# "numN" where N is a number for numpad keys, or "esc"/"escape".
# Support for any xkbcommon key is planned for a future update.
# The keybind that will reload your config.
reload_keybind = { modifiers = ["Ctrl", "Alt"], key = "r" }
# The keybind that will kill Pinnacle.
kill_keybind = { modifiers = ["Ctrl", "Alt", "Shift"], key = "escape" }
### Socket directory ###
# Pinnacle will open a Unix socket at `$XDG_RUNTIME_DIR` by default, falling back to `/tmp` if it doesn't exist.
# If you want/need to change this, use the `socket_dir` setting set to the directory of your choosing.
#
# socket_dir = "/your/dir/here/"
### Environment Variables ###
# If you need to spawn your config with any environment variables, list them here.
[envs]
# key = "value"

View file

@ -31,5 +31,6 @@ build = {
["pinnacle.signal"] = "pinnacle/signal.lua", ["pinnacle.signal"] = "pinnacle/signal.lua",
["pinnacle.layout"] = "pinnacle/layout.lua", ["pinnacle.layout"] = "pinnacle/layout.lua",
["pinnacle.render"] = "pinnacle/render.lua", ["pinnacle.render"] = "pinnacle/render.lua",
["pinnacle.snowcap"] = "pinnacle/snowcap.lua",
}, },
} }

View file

@ -26,6 +26,8 @@ local pinnacle = {
layout = require("pinnacle.layout"), layout = require("pinnacle.layout"),
---@type Render ---@type Render
render = require("pinnacle.render"), render = require("pinnacle.render"),
---@type pinnacle.Snowcap
snowcap = require("pinnacle.snowcap"),
} }
---Quit Pinnacle. ---Quit Pinnacle.
@ -52,6 +54,13 @@ end
---@see Pinnacle.run ---@see Pinnacle.run
function pinnacle.setup(config_fn) function pinnacle.setup(config_fn)
require("pinnacle.grpc.protobuf").build_protos() require("pinnacle.grpc.protobuf").build_protos()
local success, snowcap = pcall(require, "snowcap")
if success then
snowcap.init()
end
-- Make Snowcap use Pinnacle's cqueues loop
require("snowcap.grpc.client").loop = client.loop
-- This function ensures a config won't run forever if Pinnacle is killed -- This function ensures a config won't run forever if Pinnacle is killed
-- and doesn't kill configs on drop. -- and doesn't kill configs on drop.
@ -90,6 +99,10 @@ end
---@param run_fn fun(pinnacle: Pinnacle) ---@param run_fn fun(pinnacle: Pinnacle)
function pinnacle.run(run_fn) function pinnacle.run(run_fn)
require("pinnacle.grpc.protobuf").build_protos() require("pinnacle.grpc.protobuf").build_protos()
local success, snowcap = pcall(require, "snowcap")
if success then
snowcap.init()
end
run_fn(pinnacle) run_fn(pinnacle)
end end

View file

@ -190,7 +190,20 @@ function input.keybind_descriptions()
local ret = {} local ret = {}
for _, desc in ipairs(descs) do for _, desc in ipairs(descs) do
desc.modifiers = desc.modifiers or {} local mods = {}
for _, mod in ipairs(desc.modifiers or {}) do
if mod == modifier_values.shift then
table.insert(mods, "shift")
elseif mod == modifier_values.ctrl then
table.insert(mods, "ctrl")
elseif mod == modifier_values.alt then
table.insert(mods, "alt")
elseif mod == modifier_values.super then
table.insert(mods, "super")
end
end
desc.modifiers = mods
table.insert(ret, desc) table.insert(ret, desc)
end end

View file

@ -0,0 +1,349 @@
local integration = {}
---The Snowcap widget system, integrated into Pinnacle.
---@class pinnacle.Snowcap
local snowcap = {
layer = require("snowcap.layer"),
widget = require("snowcap.widget"),
input = {
key = require("snowcap.input.keys"),
},
integration = integration,
}
---@class pinnacle.snowcap.integration.QuitPrompt
---@field border_radius number
---@field border_thickness number
---@field background_color snowcap.Color
---@field border_color snowcap.Color
---@field font snowcap.Font
---@field width integer
---@field height integer
local QuitPrompt = {}
---@class pinnacle.snowcap.integration.KeybindOverlay
---@field border_radius number
---@field border_thickness number
---@field background_color snowcap.Color
---@field border_color snowcap.Color
---@field font snowcap.Font
---@field width integer
---@field height integer
local KeybindOverlay = {}
---Show this quit prompt.
function QuitPrompt:show()
local Widget = require("snowcap.widget")
local Layer = require("snowcap.layer")
local quit_font = require("pinnacle.util").deep_copy(self.font)
quit_font.weight = Widget.font.weight.BOLD
local prompt = Widget.container({
width = Widget.length.Fill,
height = Widget.length.Fill,
valign = Widget.alignment.CENTER,
halign = Widget.alignment.CENTER,
border_radius = self.border_radius,
border_thickness = self.border_thickness,
border_color = self.border_color,
background_color = self.background_color,
child = Widget.column({
children = {
Widget.text({
text = "Quit Pinnacle?",
font = quit_font,
size = 20.0,
}),
Widget.text({ text = "", size = 8.0 }),
Widget.text({
text = "Press ENTER to confirm, or\nany other key to close this",
font = self.font,
size = 14.0,
}),
},
}),
})
local prompt = Layer.new_widget({
widget = prompt,
width = self.width,
height = self.height,
anchor = nil,
keyboard_interactivity = Layer.keyboard_interactivity.EXCLUSIVE,
exclusive_zone = "respect",
layer = Layer.zlayer.OVERLAY,
})
prompt:on_key_press(function(_, key)
if key == require("snowcap.input.keys").Return then
require("pinnacle").quit()
else
prompt:close()
end
end)
end
---Show this keybind overlay.
function KeybindOverlay:show()
local descriptions = require("pinnacle.input").keybind_descriptions()
---@param mods Modifier[]
---@param xkb_name string
---@return string
local function keybind_to_string(mods, xkb_name)
local repr = {}
for _, mod in ipairs(mods) do
if mod == "super" then
table.insert(repr, "Super")
break
end
end
for _, mod in ipairs(mods) do
if mod == "ctrl" then
table.insert(repr, "Ctrl")
break
end
end
for _, mod in ipairs(mods) do
if mod == "alt" then
table.insert(repr, "Alt")
break
end
end
for _, mod in ipairs(mods) do
if mod == "shift" then
table.insert(repr, "Shift")
break
end
end
table.insert(repr, xkb_name)
return table.concat(repr, " + ")
end
---@type { group: string?, data: { keybind: string, descs: string[] }[] }[]
local groups = {}
for _, desc in ipairs(descriptions) do
local repr = keybind_to_string(desc.modifiers, desc.xkb_name)
for _, group in ipairs(groups) do
if group.group == desc.group then
for _, keybind in ipairs(group.data) do
if keybind.keybind == repr then
if desc.description then
table.insert(keybind.descs, desc.description)
end
goto continue
end
end
table.insert(group.data, { keybind = repr, descs = { desc.description } })
goto continue
end
end
table.insert(
groups,
{ group = desc.group, data = { { keybind = repr, descs = { desc.description } } } }
)
::continue::
end
-- List keybinds without a group last
local pos = nil
for i, group in ipairs(groups) do
if not group.group then
pos = i
break
end
end
if pos then
local other = table.remove(groups, pos)
table.insert(groups, other)
end
--
---@type snowcap.WidgetDef[]
local sections = {}
local Widget = require("snowcap.widget")
local bold_font = require("pinnacle.util").deep_copy(self.font)
bold_font.weight = Widget.font.weight.BOLD
for _, group in ipairs(groups) do
local group_name = group.group or "Other"
table.insert(sections, Widget.text({ text = group_name, font = bold_font, size = 19.0 }))
for _, keybind in ipairs(group.data) do
local repr = keybind.keybind
local descs = keybind.descs
if #descs == 0 then
table.insert(sections, Widget.text({ text = repr, font = self.font }))
elseif #descs == 1 then
table.insert(
sections,
Widget.row({
children = {
Widget.text({
text = repr,
width = Widget.length.FillPortion(1),
font = self.font,
}),
Widget.text({
text = descs[1],
width = Widget.length.FillPortion(2),
font = self.font,
}),
},
})
)
else
local children = {}
table.insert(
children,
Widget.text({
text = repr .. ":",
font = self.font,
})
)
for _, desc in descs do
table.insert(
children,
Widget.text({
text = "\t" .. desc,
font = self.font,
})
)
end
table.insert(
sections,
Widget.column({
children = children,
})
)
end
end
table.insert(sections, Widget.text({ text = "", size = 8.0 }))
end
local scrollable = Widget.scrollable({
child = Widget.column({
children = sections,
}),
width = Widget.length.Fill,
height = Widget.length.Fill,
})
local overlay = Widget.container({
child = Widget.column({
children = {
Widget.text({
text = "Keybinds",
font = bold_font,
size = 24.0,
width = Widget.length.Fill,
}),
Widget.text({
text = "",
size = 8.0,
}),
scrollable,
},
}),
width = Widget.length.Fill,
height = Widget.length.Fill,
padding = {
top = 16.0,
left = 16.0,
bottom = 16.0,
right = 16.0,
},
valign = Widget.alignment.CENTER,
halign = Widget.alignment.CENTER,
border_radius = self.border_radius,
border_color = self.border_color,
border_thickness = self.border_thickness,
background_color = self.background_color,
})
local Layer = require("snowcap.layer")
local overlay = Layer.new_widget({
widget = overlay,
width = self.width,
height = self.height,
anchor = nil,
keyboard_interactivity = Layer.keyboard_interactivity.EXCLUSIVE,
exclusive_zone = "respect",
layer = Layer.zlayer.OVERLAY,
})
overlay:on_key_press(function(_, _)
overlay:close()
end)
end
---Creates the default quit prompt.
---
---Some of its characteristics can be changed by altering its fields.
---@return pinnacle.snowcap.integration.QuitPrompt
function integration.quit_prompt()
local Widget = require("snowcap.widget")
---@type pinnacle.snowcap.integration.QuitPrompt
local prompt = {
border_radius = 12.0,
border_thickness = 6.0,
background_color = Widget.color.from_rgba(0.15, 0.03, 0.1, 0.65),
border_color = Widget.color.from_rgba(0.8, 0.2, 0.4),
font = {
family = Widget.font.family.Name("Ubuntu"),
},
width = 220,
height = 120,
}
setmetatable(prompt, { __index = QuitPrompt })
return prompt
end
---Creates the default keybind overlay.
---
---Some of its characteristics can be changed by altering its fields.
---@return pinnacle.snowcap.integration.KeybindOverlay
function integration.keybind_overlay()
local Widget = require("snowcap.widget")
---@type pinnacle.snowcap.integration.KeybindOverlay
local prompt = {
border_radius = 12.0,
border_thickness = 6.0,
background_color = Widget.color.from_rgba(0.15, 0.15, 0.225, 0.8),
border_color = Widget.color.from_rgba(0.4, 0.4, 0.7),
font = {
family = Widget.font.family.Name("Ubuntu"),
},
width = 700,
height = 500,
}
setmetatable(prompt, { __index = KeybindOverlay })
return prompt
end
return snowcap

View file

@ -166,22 +166,23 @@ impl KeybindOverlay {
impl std::fmt::Display for KeybindRepr { impl std::fmt::Display for KeybindRepr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let bind = self let mut parts = Vec::new();
.mods if self.mods.contains(&Mod::Super) {
.iter() parts.push("Super");
.map(|m| { }
// TODO: strum name or something if self.mods.contains(&Mod::Ctrl) {
match m { parts.push("Ctrl");
Mod::Shift => "Shift", }
Mod::Ctrl => "Ctrl", if self.mods.contains(&Mod::Alt) {
Mod::Alt => "Alt", parts.push("Alt");
Mod::Super => "Super", }
} if self.mods.contains(&Mod::Shift) {
.to_string() parts.push("Shift");
}) }
.chain([self.name.clone()])
.collect::<Vec<_>>() parts.push(self.name.as_str());
.join(" + ");
let bind = parts.join(" + ");
write!(f, "{bind}") write!(f, "{bind}")
} }
} }

View file

@ -11,7 +11,7 @@ list:
@just --list --unsorted @just --list --unsorted
# Install the configs, protobuf definitions, and the Lua library (requires Luarocks) # Install the configs, protobuf definitions, and the Lua library (requires Luarocks)
install: install-configs install-protos install-lua-lib install: install-configs install-protos install-lua-lib install-snowcap
# Install the default Lua and Rust configs # Install the default Lua and Rust configs
install-configs: install-configs:
@ -41,7 +41,7 @@ install-lua-lib:
luarocks make --local --lua-version "{{lua_version}}" luarocks make --local --lua-version "{{lua_version}}"
# Remove installed configs and the Lua API (requires Luarocks) # Remove installed configs and the Lua API (requires Luarocks)
clean: clean: clean-snowcap
rm -rf "{{xdg_data_dir}}" rm -rf "{{xdg_data_dir}}"
-luarocks remove --local pinnacle-api -luarocks remove --local pinnacle-api
@ -119,3 +119,15 @@ wlcs *args: compile-wlcs
set -euxo pipefail set -euxo pipefail
cargo build -p wlcs_pinnacle cargo build -p wlcs_pinnacle
RUST_BACKTRACE=1 ./wlcs/wlcs target/debug/libwlcs_pinnacle.so {{args}} RUST_BACKTRACE=1 ./wlcs/wlcs target/debug/libwlcs_pinnacle.so {{args}}
install-snowcap:
#!/usr/bin/env bash
set -euxo pipefail
cd "{{rootdir}}/snowcap"
just install
clean-snowcap:
#!/usr/bin/env bash
set -euxo pipefail
cd "{{rootdir}}/snowcap"
just clean

@ -1 +1 @@
Subproject commit 3e79a16e1065f07001b08fabdc763bb274cac394 Subproject commit e901659a86ec41c6903f8c913da711e1b8d95a56

View file

@ -46,7 +46,7 @@ async fn main() -> anyhow::Result<()> {
let env_filter = EnvFilter::try_from_default_env(); let env_filter = EnvFilter::try_from_default_env();
let file_log_env_filter = let file_log_env_filter =
EnvFilter::new("debug,h2=warn,hyper=warn,smithay::xwayland::xwm=warn,wgpu_hal=warn,naga=warn,wgpu_core=warn,cosmic_text=warn,iced_wgpu=warn,sctk=warn"); EnvFilter::new("debug,h2=warn,hyper=warn,smithay::xwayland::xwm=warn,wgpu_hal=warn,naga=warn,wgpu_core=warn,cosmic_text=warn,iced_wgpu=warn,sctk=error");
let file_log_layer = tracing_subscriber::fmt::layer() let file_log_layer = tracing_subscriber::fmt::layer()
.compact() .compact()
@ -55,7 +55,7 @@ async fn main() -> anyhow::Result<()> {
.with_filter(file_log_env_filter); .with_filter(file_log_env_filter);
let stdout_env_filter = let stdout_env_filter =
env_filter.unwrap_or_else(|_| EnvFilter::new("warn,pinnacle=info,snowcap=info")); env_filter.unwrap_or_else(|_| EnvFilter::new("warn,pinnacle=info,snowcap=info,sctk=error"));
let stdout_layer = tracing_subscriber::fmt::layer() let stdout_layer = tracing_subscriber::fmt::layer()
.compact() .compact()
.with_writer(std::io::stdout) .with_writer(std::io::stdout)