quickshell
This commit is contained in:
@@ -4,16 +4,17 @@ fi
|
||||
|
||||
ZSH_DISABLE_COMPFIX="true" #root comp
|
||||
ZSH_THEME="powerlevel10k/powerlevel10k"
|
||||
#
|
||||
# If you come from bash you might have to change your $PATH.
|
||||
# export PATH=$HOME/bin:/usr/local/bin:$PATH
|
||||
|
||||
export ZSH="$HOME/.oh-my-zsh"
|
||||
export ZSH_COMPDUMP=$ZSH/cache/.zcompdump-$HOST
|
||||
export EDITOR='nvim'
|
||||
export VISUAL='nvim'
|
||||
|
||||
export PATH="$HOME/scripts:$PATH"
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
export PATH="$HOME/.cargo/bin:$PATH"
|
||||
export PATH="$HOME/go/bin/:$PATH"
|
||||
|
||||
export PYTHONUNBUFFERED=1
|
||||
export QT_QPA_PLATFORMTHEME=qt5ct
|
||||
export MANPAGER="sh -c 'awk '\''{ gsub(/\x1B\[[0-9;]*m/, \"\", \$0); gsub(/.\x08/, \"\", \$0); print }'\'' | bat -p -lman'"
|
||||
|
||||
@@ -137,6 +137,11 @@ windowrule = float on, size 413 689, match:class ^(io.bassi.Amberol)$
|
||||
windowrule = float on, match:class ^(qalculate-gtk)$, match:title ^(Qalculate!)$
|
||||
# windowrule = tile on, workspace 10, match:class ^(affinity.exe)$
|
||||
|
||||
# QuickShell
|
||||
# windowrule = float on, match:title ^(qs-volume)$
|
||||
# windowrule = no_shadow on, match:title ^(qs-volume)$
|
||||
# windowrule = border_size 0, match:title ^(qs-volume)$
|
||||
|
||||
$mainMod = SUPER
|
||||
|
||||
bind = $mainMod, R, exec, $terminal
|
||||
|
||||
+13
-10
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"LuaSnip": { "branch": "master", "commit": "642b0c595e11608b4c18219e93b88d7637af27bc" },
|
||||
"LuaSnip": { "branch": "master", "commit": "a62e1083a3cfe8b6b206e7d3d33a51091df25357" },
|
||||
"barbar.nvim": { "branch": "master", "commit": "539d73def39c9172b4d4d769f14090e08f37b29d" },
|
||||
"buffer-vacuum": { "branch": "main", "commit": "917986d31cce6501c929ab6088c0887beff56b69" },
|
||||
"catppuccin": { "branch": "main", "commit": "16408d203973da7e43fab87c6f9d3bf7fa98fa8b" },
|
||||
"catppuccin": { "branch": "main", "commit": "426dbebe06b5c69fd846ceb17b42e12f890aedf1" },
|
||||
"cmp-buffer": { "branch": "main", "commit": "b74fab3656eea9de20a9b8116afa3cfc4ec09657" },
|
||||
"cmp-nvim-lsp": { "branch": "main", "commit": "cbc7b02bb99fae35cb42f514762b89b5126651ef" },
|
||||
"cmp-nvim-lua": { "branch": "main", "commit": "e3a22cb071eb9d6508a156306b102c45cd2d573d" },
|
||||
@@ -13,29 +13,32 @@
|
||||
"indent-blankline.nvim": { "branch": "master", "commit": "d28a3f70721c79e3c5f6693057ae929f3d9c0a03" },
|
||||
"lazy.nvim": { "branch": "main", "commit": "306a05526ada86a7b30af95c5cc81ffba93fef97" },
|
||||
"local-highlight.nvim": { "branch": "master", "commit": "bdf0e15df9164b507cb9e9ba781ba29f2dd3f4a6" },
|
||||
"lualine.nvim": { "branch": "master", "commit": "74114f0df664f14d7c228945693ba68a3b70a794" },
|
||||
"lualine.nvim": { "branch": "master", "commit": "f5d2a8570f8b736ddb9bb4be504355bcd6e15ec8" },
|
||||
"markdown-preview.nvim": { "branch": "master", "commit": "a923f5fc5ba36a3b17e289dc35dc17f66d0548ee" },
|
||||
"mason.nvim": { "branch": "main", "commit": "44d1e90e1f66e077268191e3ee9d2ac97cc18e65" },
|
||||
"mason.nvim": { "branch": "main", "commit": "b03fb0f20bc1d43daf558cda981a2be22e73ac42" },
|
||||
"neo-tree.nvim": { "branch": "v3.x", "commit": "84c75e7a7e443586f60508d12fc50f90d9aee14e" },
|
||||
"none-ls-extras.nvim": { "branch": "main", "commit": "c6fa39ac52814182c05552cb5d3750cae23ff0f0" },
|
||||
"none-ls.nvim": { "branch": "main", "commit": "6ad251ef07dd4ba0cc725a5553cc76e9923f1a4a" },
|
||||
"none-ls-extras.nvim": { "branch": "main", "commit": "14fa31ce8c0268a3b2c9cc14979ecf771982d433" },
|
||||
"none-ls.nvim": { "branch": "main", "commit": "7f9301e416533b5d74e2fb3b1ce5059eeaed748b" },
|
||||
"nui.nvim": { "branch": "main", "commit": "de740991c12411b663994b2860f1a4fd0937c130" },
|
||||
"nvim-autopairs": { "branch": "master", "commit": "59bce2eef357189c3305e25bc6dd2d138c1683f5" },
|
||||
"nvim-cmp": { "branch": "main", "commit": "a1d504892f2bc56c2e79b65c6faded2fd21f3eca" },
|
||||
"nvim-colorizer.lua": { "branch": "master", "commit": "c8c2c19436d33165ec34fa90c975bc0a739fe3fb" },
|
||||
"nvim-lspconfig": { "branch": "master", "commit": "9ccd58a7949091c0cc2777d4e92a45a209c808c1" },
|
||||
"nvim-colorizer.lua": { "branch": "master", "commit": "5cfe7fffbd01e17b3c1e14af85d5febdef88bd8c" },
|
||||
"nvim-lspconfig": { "branch": "master", "commit": "c588db330592fa477a70d2fee6ba20a57194bdc3" },
|
||||
"nvim-treesitter": { "branch": "master", "commit": "cf12346a3414fa1b06af75c79faebe7f76df080a" },
|
||||
"nvim-treesitter-textobjects": { "branch": "master", "commit": "5ca4aaa6efdcc59be46b95a3e876300cfead05ef" },
|
||||
"nvim-ts-autotag": { "branch": "main", "commit": "8e1c0a389f20bf7f5b0dd0e00306c1247bda2595" },
|
||||
"nvim-web-devicons": { "branch": "master", "commit": "40e9d5a6cc3db11b309e39593fc7ac03bb844e38" },
|
||||
"nvim-web-devicons": { "branch": "master", "commit": "95b7a002d5dba1a42eb58f5fac5c565a485eefd0" },
|
||||
"oil.nvim": { "branch": "master", "commit": "0fcc83805ad11cf714a949c98c605ed717e0b83e" },
|
||||
"plenary.nvim": { "branch": "master", "commit": "b9fd5226c2f76c951fc8ed5923d85e4de065e509" },
|
||||
"rainbow-delimiters.nvim": { "branch": "master", "commit": "50080ed0f44dbc18ae13b81278a01b951a06127a" },
|
||||
"rainbow-delimiters.nvim": { "branch": "master", "commit": "aab6caaffd79b8def22ec4320a5344f7c42f58d2" },
|
||||
"ruffer": { "branch": "plugin", "commit": "5248604605bf1039147f3f29dcd46aa296557e9c" },
|
||||
"telescope-ui-select.nvim": { "branch": "master", "commit": "6e51d7da30bd139a6950adf2a47fda6df9fa06d2" },
|
||||
"telescope.nvim": { "branch": "master", "commit": "a0bbec21143c7bc5f8bb02e0005fa0b982edc026" },
|
||||
"undotree": { "branch": "master", "commit": "6fa6b57cda8459e1e4b2ca34df702f55242f4e4d" },
|
||||
"vim-commentary": { "branch": "master", "commit": "64a654ef4a20db1727938338310209b6a63f60c9" },
|
||||
"vim-dadbod": { "branch": "master", "commit": "6d1d41da4873a445c5605f2005ad2c68c99d8770" },
|
||||
"vim-dadbod-completion": { "branch": "master", "commit": "a8dac0b3cf6132c80dc9b18bef36d4cf7a9e1fe6" },
|
||||
"vim-dadbod-ui": { "branch": "master", "commit": "07e92e22114cc5b1ba4938d99897d85b58e20475" },
|
||||
"vim-devicons": { "branch": "master", "commit": "71f239af28b7214eebb60d4ea5bd040291fb7e33" },
|
||||
"vim-floaterm": { "branch": "master", "commit": "0ab5eb8135dc884bc543a819ac7033c15e72a76b" },
|
||||
"vim-signature": { "branch": "master", "commit": "6bc3dd1294a22e897f0dcf8dd72b85f350e306bc" },
|
||||
|
||||
@@ -1,80 +1,85 @@
|
||||
local cmp = require'cmp'
|
||||
local cmp = require("cmp")
|
||||
|
||||
local kind_icons = {
|
||||
Text = " ",
|
||||
Method = " ",
|
||||
Function = " ",
|
||||
Constructor = " ",
|
||||
Field = " ",
|
||||
Variable = " ",
|
||||
Class = " ",
|
||||
Interface = " ",
|
||||
Module = " ",
|
||||
Property = " ",
|
||||
Unit = " ",
|
||||
Value = " ",
|
||||
Enum = " ",
|
||||
Keyword = " ",
|
||||
Snippet = " ",
|
||||
Color = " ",
|
||||
File = " ",
|
||||
Reference = " ",
|
||||
Folder = " ",
|
||||
EnumMember = " ",
|
||||
Constant = " ",
|
||||
Struct = " ",
|
||||
Event = " ",
|
||||
Operator = " ",
|
||||
TypeParameter = " ",
|
||||
Text = " ",
|
||||
Method = " ",
|
||||
Function = " ",
|
||||
Constructor = " ",
|
||||
Field = " ",
|
||||
Variable = " ",
|
||||
Class = "",
|
||||
Interface = " ",
|
||||
Module = " ",
|
||||
Property = " ",
|
||||
Unit = " ",
|
||||
Value = "",
|
||||
Enum = " ",
|
||||
Keyword = "",
|
||||
Snippet = " ",
|
||||
Color = "",
|
||||
File = "",
|
||||
Reference = " ",
|
||||
Folder = "",
|
||||
EnumMember = " ",
|
||||
Constant = "",
|
||||
Struct = " ",
|
||||
Event = "",
|
||||
Operator = "",
|
||||
TypeParameter = "",
|
||||
}
|
||||
|
||||
|
||||
cmp.setup({
|
||||
snippet = {
|
||||
expand = function(args)
|
||||
require('luasnip').lsp_expand(args.body) -- For `luasnip` users.
|
||||
end,
|
||||
},
|
||||
formatting = {
|
||||
format = function(_, vim_item)
|
||||
vim_item.kind = (kind_icons[vim_item.kind] or '') .. vim_item.kind
|
||||
return vim_item
|
||||
end,
|
||||
},
|
||||
window = {
|
||||
completion = cmp.config.window.bordered(),
|
||||
documentation = cmp.config.window.bordered(),
|
||||
},
|
||||
mapping = {
|
||||
['<Down>'] = cmp.mapping.select_next_item({ behavior = cmp.SelectBehavior.Select }),
|
||||
['<A-j>'] = cmp.mapping.select_next_item({ behavior = cmp.SelectBehavior.Select }),
|
||||
['<Up>'] = cmp.mapping.select_prev_item({ behavior = cmp.SelectBehavior.Select }),
|
||||
['<A-k>'] = cmp.mapping.select_prev_item({ behavior = cmp.SelectBehavior.Select }),
|
||||
['<A-d>'] = cmp.mapping.scroll_docs(-4),
|
||||
['<A-f>'] = cmp.mapping.scroll_docs(4),
|
||||
['<A-Space>'] = cmp.mapping.complete(),
|
||||
['<A-e>'] = cmp.mapping.close(),
|
||||
['<CR>'] = cmp.mapping.confirm({
|
||||
behavior = cmp.ConfirmBehavior.Insert,
|
||||
select = true,
|
||||
}),
|
||||
snippet = {
|
||||
expand = function(args)
|
||||
require("luasnip").lsp_expand(args.body) -- For `luasnip` users.
|
||||
end,
|
||||
},
|
||||
formatting = {
|
||||
format = function(_, vim_item)
|
||||
vim_item.kind = (kind_icons[vim_item.kind] or "") .. vim_item.kind
|
||||
return vim_item
|
||||
end,
|
||||
},
|
||||
window = {
|
||||
completion = cmp.config.window.bordered(),
|
||||
documentation = cmp.config.window.bordered(),
|
||||
},
|
||||
mapping = {
|
||||
["<Down>"] = cmp.mapping.select_next_item({ behavior = cmp.SelectBehavior.Select }),
|
||||
["<A-j>"] = cmp.mapping.select_next_item({ behavior = cmp.SelectBehavior.Select }),
|
||||
["<Up>"] = cmp.mapping.select_prev_item({ behavior = cmp.SelectBehavior.Select }),
|
||||
["<A-k>"] = cmp.mapping.select_prev_item({ behavior = cmp.SelectBehavior.Select }),
|
||||
["<A-d>"] = cmp.mapping.scroll_docs(-4),
|
||||
["<A-f>"] = cmp.mapping.scroll_docs(4),
|
||||
["<A-Space>"] = cmp.mapping.complete(),
|
||||
["<A-e>"] = cmp.mapping.close(),
|
||||
["<CR>"] = cmp.mapping.confirm({
|
||||
behavior = cmp.ConfirmBehavior.Insert,
|
||||
select = true,
|
||||
}),
|
||||
|
||||
['<Tab>'] = cmp.mapping(function(fallback)
|
||||
if cmp.visible() then
|
||||
cmp.select_next_item()
|
||||
else
|
||||
fallback()
|
||||
end
|
||||
end, { 'i', 's' }),
|
||||
["<Tab>"] = cmp.mapping(function(fallback)
|
||||
if cmp.visible() then
|
||||
cmp.select_next_item()
|
||||
else
|
||||
fallback()
|
||||
end
|
||||
end, { "i", "s" }),
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
sources = cmp.config.sources({
|
||||
{ name = 'nvim_lsp' },
|
||||
{ name = 'luasnip' }, -- For luasnip users.
|
||||
{ name = 'path' }
|
||||
},
|
||||
{
|
||||
{ name = 'buffer' },
|
||||
})
|
||||
sources = cmp.config.sources({
|
||||
{ name = "nvim_lsp" },
|
||||
{ name = "luasnip" }, -- For luasnip users.
|
||||
{ name = "path" },
|
||||
}, {
|
||||
{ name = "buffer" },
|
||||
}),
|
||||
})
|
||||
|
||||
--dadbod
|
||||
cmp.setup.filetype({ "sql" }, {
|
||||
sources = {
|
||||
{ name = "vim-dadbod-completion" },
|
||||
{ name = "buffer" },
|
||||
},
|
||||
})
|
||||
|
||||
+160
-149
@@ -1,191 +1,202 @@
|
||||
|
||||
-- Bootstrap lazy.nvim
|
||||
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
|
||||
if not (vim.uv or vim.loop).fs_stat(lazypath) then
|
||||
local lazyrepo = "https://github.com/folke/lazy.nvim.git"
|
||||
local out = vim.fn.system({ "git", "clone", "--filter=blob:none", "--branch=stable", lazyrepo, lazypath })
|
||||
if vim.v.shell_error ~= 0 then
|
||||
vim.api.nvim_echo({
|
||||
{ "Failed to clone lazy.nvim:\n", "ErrorMsg" },
|
||||
{ out, "WarningMsg" },
|
||||
{ "\nPress any key to exit..." },
|
||||
}, true, {})
|
||||
vim.fn.getchar()
|
||||
os.exit(1)
|
||||
end
|
||||
local lazyrepo = "https://github.com/folke/lazy.nvim.git"
|
||||
local out = vim.fn.system({ "git", "clone", "--filter=blob:none", "--branch=stable", lazyrepo, lazypath })
|
||||
if vim.v.shell_error ~= 0 then
|
||||
vim.api.nvim_echo({
|
||||
{ "Failed to clone lazy.nvim:\n", "ErrorMsg" },
|
||||
{ out, "WarningMsg" },
|
||||
{ "\nPress any key to exit..." },
|
||||
}, true, {})
|
||||
vim.fn.getchar()
|
||||
os.exit(1)
|
||||
end
|
||||
end
|
||||
vim.opt.rtp:prepend(lazypath)
|
||||
|
||||
local plugins = {
|
||||
|
||||
{ "neovim/nvim-lspconfig" },
|
||||
{ "neovim/nvim-lspconfig" },
|
||||
|
||||
-- mine
|
||||
{
|
||||
'acidburnmonkey/ruffer',
|
||||
config = function()
|
||||
require('ruffer').setup()
|
||||
end,
|
||||
},
|
||||
-- mine
|
||||
{
|
||||
"acidburnmonkey/ruffer",
|
||||
config = function()
|
||||
require("ruffer").setup()
|
||||
end,
|
||||
},
|
||||
|
||||
-- Terminal floating window
|
||||
{ "voldikss/vim-floaterm" },
|
||||
-- Terminal floating window
|
||||
{ "voldikss/vim-floaterm" },
|
||||
|
||||
-- Indent guides
|
||||
{
|
||||
"lukas-reineke/indent-blankline.nvim",
|
||||
main = "ibl",
|
||||
opts = {},
|
||||
},
|
||||
-- Indent guides
|
||||
{
|
||||
"lukas-reineke/indent-blankline.nvim",
|
||||
main = "ibl",
|
||||
opts = {},
|
||||
},
|
||||
|
||||
-- Color highlighting
|
||||
{ "catgoose/nvim-colorizer.lua"},
|
||||
-- Color highlighting
|
||||
{ "catgoose/nvim-colorizer.lua" },
|
||||
|
||||
-- Undo tree viewer
|
||||
{ "mbbill/undotree" },
|
||||
-- Undo tree viewer
|
||||
{ "mbbill/undotree" },
|
||||
|
||||
-- Devicons
|
||||
{ "nvim-tree/nvim-web-devicons", opts = {} },
|
||||
-- Devicons
|
||||
{ "nvim-tree/nvim-web-devicons", opts = {} },
|
||||
|
||||
-- Neo-tree file explorer
|
||||
{
|
||||
"nvim-neo-tree/neo-tree.nvim",
|
||||
branch = "v3.x",
|
||||
dependencies = {
|
||||
"nvim-lua/plenary.nvim",
|
||||
"nvim-tree/nvim-web-devicons",
|
||||
"MunifTanjim/nui.nvim",
|
||||
},
|
||||
lazy = true,
|
||||
},
|
||||
-- Neo-tree file explorer
|
||||
{
|
||||
"nvim-neo-tree/neo-tree.nvim",
|
||||
branch = "v3.x",
|
||||
dependencies = {
|
||||
"nvim-lua/plenary.nvim",
|
||||
"nvim-tree/nvim-web-devicons",
|
||||
"MunifTanjim/nui.nvim",
|
||||
},
|
||||
lazy = true,
|
||||
},
|
||||
|
||||
-- Telescope fuzzy finder
|
||||
{
|
||||
'nvim-telescope/telescope.nvim', tag = '0.1.8',
|
||||
dependencies = { 'nvim-lua/plenary.nvim' }
|
||||
},
|
||||
-- Telescope fuzzy finder
|
||||
{
|
||||
"nvim-telescope/telescope.nvim",
|
||||
tag = "0.1.8",
|
||||
dependencies = { "nvim-lua/plenary.nvim" },
|
||||
},
|
||||
|
||||
-- Treesitter
|
||||
{
|
||||
"nvim-treesitter/nvim-treesitter",
|
||||
build = ":TSUpdate",
|
||||
},
|
||||
{ "nvim-treesitter/nvim-treesitter-textobjects" },
|
||||
-- Treesitter
|
||||
{
|
||||
"nvim-treesitter/nvim-treesitter",
|
||||
build = ":TSUpdate",
|
||||
},
|
||||
{ "nvim-treesitter/nvim-treesitter-textobjects" },
|
||||
|
||||
-- Mason package manager
|
||||
{
|
||||
"mason-org/mason.nvim",
|
||||
opts = {},
|
||||
},
|
||||
|
||||
-- Mason package manager
|
||||
{
|
||||
"mason-org/mason.nvim",
|
||||
opts = {}
|
||||
},
|
||||
-- LuaSnip engine
|
||||
{
|
||||
"L3MON4D3/LuaSnip",
|
||||
build = "make install_jsregexp",
|
||||
},
|
||||
-- LuaSnip snippets
|
||||
{ "rafamadriz/friendly-snippets" },
|
||||
|
||||
-- LuaSnip engine
|
||||
{
|
||||
"L3MON4D3/LuaSnip",
|
||||
build = "make install_jsregexp",
|
||||
},
|
||||
-- LuaSnip snippets
|
||||
{ "rafamadriz/friendly-snippets" },
|
||||
-- Autopairs
|
||||
{
|
||||
"windwp/nvim-autopairs",
|
||||
event = "InsertEnter",
|
||||
},
|
||||
|
||||
-- Autopairs
|
||||
{
|
||||
'windwp/nvim-autopairs',
|
||||
event = "InsertEnter",
|
||||
},
|
||||
-- Commenting utility
|
||||
{ "tpope/vim-commentary" },
|
||||
|
||||
-- Commenting utility
|
||||
{ "tpope/vim-commentary" },
|
||||
-- Catppuccin theme
|
||||
{ "catppuccin/nvim", name = "catppuccin", priority = 1000 },
|
||||
|
||||
-- Catppuccin theme
|
||||
{ "catppuccin/nvim",
|
||||
name = "catppuccin",
|
||||
priority = 1000 },
|
||||
-- Surround plugin
|
||||
{ "tpope/vim-surround" },
|
||||
|
||||
-- Surround plugin
|
||||
{ "tpope/vim-surround" },
|
||||
-- Formatter/Linter integration
|
||||
{ "nvimtools/none-ls.nvim", dependencies = {
|
||||
"nvimtools/none-ls-extras.nvim",
|
||||
} },
|
||||
|
||||
-- Formatter/Linter integration
|
||||
{ "nvimtools/none-ls.nvim" ,
|
||||
dependencies = {
|
||||
"nvimtools/none-ls-extras.nvim"}
|
||||
} ,
|
||||
-- Treesitter auto-closing tags
|
||||
{ "windwp/nvim-ts-autotag" },
|
||||
|
||||
-- Treesitter auto-closing tags
|
||||
{ "windwp/nvim-ts-autotag" },
|
||||
-- Signature marks
|
||||
{ "kshenoy/vim-signature" },
|
||||
|
||||
-- Signature marks
|
||||
{ "kshenoy/vim-signature" },
|
||||
-- Oil file explorer
|
||||
{
|
||||
"stevearc/oil.nvim",
|
||||
lazy = false,
|
||||
},
|
||||
|
||||
-- Oil file explorer
|
||||
{
|
||||
'stevearc/oil.nvim',
|
||||
lazy = false
|
||||
},
|
||||
-- Rainbow delimiters
|
||||
{ "HiPhish/rainbow-delimiters.nvim" },
|
||||
|
||||
-- Rainbow delimiters
|
||||
{ "HiPhish/rainbow-delimiters.nvim" },
|
||||
-- Completion engine
|
||||
{ "hrsh7th/nvim-cmp" },
|
||||
{ "hrsh7th/cmp-buffer" },
|
||||
{ "hrsh7th/cmp-path" },
|
||||
{ "saadparwaiz1/cmp_luasnip" },
|
||||
{ "hrsh7th/cmp-nvim-lsp" },
|
||||
{ "hrsh7th/cmp-nvim-lua" },
|
||||
|
||||
-- Completion engine
|
||||
{ "hrsh7th/nvim-cmp" },
|
||||
{ "hrsh7th/cmp-buffer" },
|
||||
{ "hrsh7th/cmp-path" },
|
||||
{ "saadparwaiz1/cmp_luasnip" },
|
||||
{ "hrsh7th/cmp-nvim-lsp" },
|
||||
{ "hrsh7th/cmp-nvim-lua" },
|
||||
-- Lualine
|
||||
{ "nvim-lualine/lualine.nvim", dependencies = { "nvim-tree/nvim-web-devicons" } },
|
||||
|
||||
-- Lualine
|
||||
{ 'nvim-lualine/lualine.nvim',
|
||||
dependencies = { 'nvim-tree/nvim-web-devicons' } },
|
||||
-- File icons
|
||||
{ "ryanoasis/vim-devicons" },
|
||||
|
||||
-- File icons
|
||||
{ "ryanoasis/vim-devicons" },
|
||||
-- barbar
|
||||
{
|
||||
"romgrk/barbar.nvim",
|
||||
dependencies = {
|
||||
"nvim-tree/nvim-web-devicons",
|
||||
},
|
||||
init = function()
|
||||
vim.g.barbar_auto_setup = false -- disable auto-setup
|
||||
end,
|
||||
},
|
||||
|
||||
-- barbar
|
||||
{
|
||||
'romgrk/barbar.nvim',
|
||||
dependencies = {
|
||||
'nvim-tree/nvim-web-devicons'
|
||||
},
|
||||
init = function()
|
||||
vim.g.barbar_auto_setup = false -- disable auto-setup
|
||||
end
|
||||
},
|
||||
-- highlight
|
||||
{
|
||||
"tzachar/local-highlight.nvim",
|
||||
},
|
||||
|
||||
-- highlight
|
||||
{
|
||||
'tzachar/local-highlight.nvim',
|
||||
},
|
||||
-- buffer vacuum
|
||||
{
|
||||
"ChuufMaster/buffer-vacuum",
|
||||
opts = {
|
||||
max_buffers = 2,
|
||||
count_pinned_buffers = false,
|
||||
enable_messages = false,
|
||||
},
|
||||
},
|
||||
|
||||
-- buffer vacuum
|
||||
{
|
||||
'ChuufMaster/buffer-vacuum',
|
||||
opts = {
|
||||
max_buffers = 2,
|
||||
count_pinned_buffers = false,
|
||||
enable_messages = false,
|
||||
},
|
||||
},
|
||||
-- telescope plugin
|
||||
{ "nvim-telescope/telescope-ui-select.nvim" },
|
||||
|
||||
-- telescope plugin
|
||||
{'nvim-telescope/telescope-ui-select.nvim' },
|
||||
-- harpoon
|
||||
{ "ThePrimeagen/harpoon", lazy = false },
|
||||
|
||||
-- harpoon
|
||||
{'ThePrimeagen/harpoon',lazy=false},
|
||||
|
||||
--MarkdownPreview
|
||||
{
|
||||
"iamcco/markdown-preview.nvim",
|
||||
cmd = { "MarkdownPreviewToggle", "MarkdownPreview", "MarkdownPreviewStop" },
|
||||
build = "cd app && npm install",
|
||||
init = function()
|
||||
vim.g.mkdp_filetypes = { "markdown" }
|
||||
end,
|
||||
ft = { "markdown" },
|
||||
},
|
||||
--MarkdownPreview
|
||||
{
|
||||
"iamcco/markdown-preview.nvim",
|
||||
cmd = { "MarkdownPreviewToggle", "MarkdownPreview", "MarkdownPreviewStop" },
|
||||
build = "cd app && npm install",
|
||||
init = function()
|
||||
vim.g.mkdp_filetypes = { "markdown" }
|
||||
end,
|
||||
ft = { "markdown" },
|
||||
},
|
||||
|
||||
-- Dadbod
|
||||
{
|
||||
"kristijanhusak/vim-dadbod-ui",
|
||||
dependencies = {
|
||||
{ "tpope/vim-dadbod", lazy = true },
|
||||
{ "kristijanhusak/vim-dadbod-completion", ft = { "sql", "mysql", "plsql" }, lazy = true },
|
||||
},
|
||||
cmd = {
|
||||
"DBUI",
|
||||
"DBUIToggle",
|
||||
"DBUIAddConnection",
|
||||
"DBUIFindBuffer",
|
||||
},
|
||||
init = function()
|
||||
vim.g.db_ui_use_nerd_fonts = 1
|
||||
end,
|
||||
},
|
||||
}
|
||||
|
||||
local opts = {}
|
||||
|
||||
require('lazy').setup(plugins, opts)
|
||||
|
||||
require("lazy").setup(plugins, opts)
|
||||
|
||||
@@ -0,0 +1,880 @@
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Window
|
||||
import QtQuick.Effects
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
Item {
|
||||
id: window
|
||||
focus: true
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// SHORTCUTS & AUDIO
|
||||
// -------------------------------------------------------------------------
|
||||
Shortcut {
|
||||
sequence: "Tab"
|
||||
onActivated: {
|
||||
if (window.activeTab === "outputs") window.activeTab = "inputs";
|
||||
else if (window.activeTab === "inputs") window.activeTab = "apps";
|
||||
else window.activeTab = "outputs";
|
||||
}
|
||||
}
|
||||
// -------------------------------------------------------------------------
|
||||
// COLORS (Catppuccin Mocha)
|
||||
// -------------------------------------------------------------------------
|
||||
readonly property color base: "#1e1e2e"
|
||||
readonly property color mantle: "#181825"
|
||||
readonly property color crust: "#11111b"
|
||||
readonly property color text: "#cdd6f4"
|
||||
readonly property color subtext0: "#a6adc8"
|
||||
readonly property color overlay0: "#6c7086"
|
||||
readonly property color overlay1: "#7f849c"
|
||||
readonly property color surface0: "#313244"
|
||||
readonly property color surface1: "#45475a"
|
||||
readonly property color surface2: "#585b70"
|
||||
readonly property color mauve: "#cba6f7"
|
||||
readonly property color pink: "#f5c2e7"
|
||||
readonly property color red: "#f38ba8"
|
||||
readonly property color maroon: "#eba0ac"
|
||||
readonly property color peach: "#fab387"
|
||||
readonly property color yellow: "#f9e2af"
|
||||
readonly property color green: "#a6e3a1"
|
||||
readonly property color teal: "#94e2d5"
|
||||
readonly property color sapphire: "#74c7ec"
|
||||
readonly property color blue: "#89b4fa"
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// STATE & CONFIG
|
||||
// -------------------------------------------------------------------------
|
||||
readonly property string scriptsDir: Qt.resolvedUrl(".").toString().replace(/^file:\/\//, "")
|
||||
|
||||
// Dynamic height: 25(top margin) + 150(hero) + 20(spacing) + 54(tabs) + 20(spacing) + list + 25(bottom margin)
|
||||
readonly property real listH: Math.max(220, Math.min(600, contentList.contentHeight))
|
||||
implicitHeight: 294 + listH
|
||||
|
||||
property string activeTab: "outputs" // outputs, inputs, apps
|
||||
onActiveTabChanged: updateHeroData()
|
||||
|
||||
readonly property color tabColor: {
|
||||
if (activeTab === "outputs") return window.blue;
|
||||
if (activeTab === "inputs") return window.mauve;
|
||||
return window.green;
|
||||
}
|
||||
|
||||
property real globalOrbitAngle: 0
|
||||
NumberAnimation on globalOrbitAngle {
|
||||
from: 0; to: Math.PI * 2; duration: 90000; loops: Animation.Infinite; running: true
|
||||
}
|
||||
|
||||
// Top Orb Active State Links
|
||||
property string activeId: ""
|
||||
property string activeName: "No Device"
|
||||
property string activeDesc: ""
|
||||
property int activeVol: 0
|
||||
property bool activeMute: false
|
||||
property string activeIcon: ""
|
||||
|
||||
// Models
|
||||
ListModel { id: outputsModel }
|
||||
ListModel { id: inputsModel }
|
||||
ListModel { id: appsModel }
|
||||
|
||||
property var draggingNodes: ({})
|
||||
property bool draggingMaster: false
|
||||
Timer { id: syncDelay; interval: 600; onTriggered: { window.draggingNodes = ({}); window.draggingMaster = false; } }
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// CACHING & DATA LOGIC
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
function processAudioJson(textData) {
|
||||
if (!textData) return;
|
||||
try {
|
||||
let data = JSON.parse(textData);
|
||||
syncModel(outputsModel, data.outputs || []);
|
||||
syncModel(inputsModel, data.inputs || []);
|
||||
syncModel(appsModel, data.apps || []);
|
||||
updateHeroData();
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
function updateHeroData() {
|
||||
let targetModel = (window.activeTab === "inputs") ? inputsModel : outputsModel;
|
||||
|
||||
let foundDefault = false;
|
||||
for (let i = 0; i < targetModel.count; i++) {
|
||||
let d = targetModel.get(i);
|
||||
if (d.is_default) {
|
||||
window.activeId = d.id;
|
||||
window.activeName = d.description;
|
||||
window.activeDesc = d.name;
|
||||
window.activeIcon = d.icon;
|
||||
if (!window.draggingMaster) {
|
||||
window.activeVol = d.volume;
|
||||
window.activeMute = d.mute;
|
||||
}
|
||||
foundDefault = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback if no default is found
|
||||
if (!foundDefault && targetModel.count > 0) {
|
||||
let d = targetModel.get(0);
|
||||
window.activeId = d.id;
|
||||
window.activeName = d.description;
|
||||
window.activeDesc = d.name;
|
||||
window.activeIcon = d.icon;
|
||||
if (!window.draggingMaster) {
|
||||
window.activeVol = d.volume;
|
||||
window.activeMute = d.mute;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function syncModel(listModel, dataArray) {
|
||||
for (let i = listModel.count - 1; i >= 0; i--) {
|
||||
let id = listModel.get(i).id;
|
||||
let found = false;
|
||||
for (let j = 0; j < dataArray.length; j++) {
|
||||
if (id === dataArray[j].id) { found = true; break; }
|
||||
}
|
||||
if (!found) listModel.remove(i);
|
||||
}
|
||||
|
||||
for (let i = 0; i < dataArray.length; i++) {
|
||||
let d = dataArray[i];
|
||||
let foundIdx = -1;
|
||||
for (let j = i; j < listModel.count; j++) {
|
||||
if (listModel.get(j).id === d.id) { foundIdx = j; break; }
|
||||
}
|
||||
|
||||
let obj = {
|
||||
id: d.id, name: d.name, description: d.description,
|
||||
volume: d.volume, mute: d.mute, is_default: d.is_default, icon: d.icon
|
||||
};
|
||||
|
||||
if (foundIdx === -1) {
|
||||
listModel.insert(i, obj);
|
||||
} else {
|
||||
if (foundIdx !== i) listModel.move(foundIdx, i, 1);
|
||||
for (let key in obj) {
|
||||
if (key === "volume" && window.draggingNodes[obj.id]) continue;
|
||||
if (listModel.get(i)[key] !== obj[key]) {
|
||||
listModel.setProperty(i, key, obj[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: audioPoller
|
||||
command: ["python3", window.scriptsDir + "/get_audio_state.py"]
|
||||
running: true
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
processAudioJson(this.text.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 1000; running: true; repeat: true; triggeredOnStart: true;
|
||||
onTriggered: audioPoller.running = true
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// ANIMATIONS
|
||||
// -------------------------------------------------------------------------
|
||||
property real introMain: 0
|
||||
property real introHeader: 0
|
||||
property real introContent: 0
|
||||
|
||||
ParallelAnimation {
|
||||
running: true
|
||||
NumberAnimation { target: window; property: "introMain"; from: 0; to: 1.0; duration: 800; easing.type: Easing.OutExpo }
|
||||
SequentialAnimation {
|
||||
PauseAnimation { duration: 100 }
|
||||
NumberAnimation { target: window; property: "introHeader"; from: 0; to: 1.0; duration: 700; easing.type: Easing.OutBack; easing.overshoot: 1.2 }
|
||||
}
|
||||
SequentialAnimation {
|
||||
PauseAnimation { duration: 200 }
|
||||
NumberAnimation { target: window; property: "introContent"; from: 0; to: 1.0; duration: 800; easing.type: Easing.OutExpo }
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// UI LAYOUT
|
||||
// -------------------------------------------------------------------------
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
scale: 0.95 + (0.05 * introMain)
|
||||
opacity: introMain
|
||||
transform: Translate { y: 20 * (1 - introMain) }
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: 20
|
||||
color: window.base
|
||||
border.color: window.surface0
|
||||
border.width: 1
|
||||
clip: true
|
||||
|
||||
// Rotating Background Blobs
|
||||
Rectangle {
|
||||
width: parent.width * 0.8; height: width; radius: width / 2
|
||||
x: (parent.width / 2 - width / 2) + Math.cos(window.globalOrbitAngle * 2) * 150
|
||||
y: (parent.height / 2 - height / 2) + Math.sin(window.globalOrbitAngle * 2) * 100
|
||||
opacity: 0.06
|
||||
color: window.tabColor
|
||||
Behavior on color { ColorAnimation { duration: 800 } }
|
||||
}
|
||||
Rectangle {
|
||||
width: parent.width * 0.9; height: width; radius: width / 2
|
||||
x: (parent.width / 2 - width / 2) + Math.sin(window.globalOrbitAngle * 1.5) * -150
|
||||
y: (parent.height / 2 - height / 2) + Math.cos(window.globalOrbitAngle * 1.5) * -100
|
||||
opacity: 0.04
|
||||
color: Qt.lighter(window.tabColor, 1.3)
|
||||
Behavior on color { ColorAnimation { duration: 800 } }
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 25
|
||||
spacing: 20
|
||||
|
||||
// ==========================================
|
||||
// HERO ORB & MASTER SLIDER (TOP SECTION)
|
||||
// ==========================================
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 150
|
||||
opacity: introHeader
|
||||
transform: Translate { y: 30 * (1.0 - introHeader) }
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 25
|
||||
|
||||
// 1. The Orb
|
||||
Item {
|
||||
Layout.preferredWidth: 130
|
||||
Layout.preferredHeight: 130
|
||||
scale: masterOrbMa.pressed ? 0.95 : (masterOrbMa.containsMouse ? 1.05 : 1.0)
|
||||
Behavior on scale { NumberAnimation { duration: 400; easing.type: Easing.OutBack } }
|
||||
|
||||
// Outermost border pulse ring
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width + 15
|
||||
height: width
|
||||
radius: width / 2
|
||||
color: "transparent"
|
||||
border.color: window.activeMute ? window.red : window.tabColor
|
||||
border.width: 3
|
||||
z: -2
|
||||
|
||||
property real pulseOp: 0.0
|
||||
property real pulseSc: 1.0
|
||||
opacity: window.activeMute ? 0.0 : pulseOp
|
||||
scale: pulseSc
|
||||
|
||||
Timer {
|
||||
interval: 45
|
||||
running: parent.opacity > 0.01 || !window.activeMute
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
var time = Date.now() / 1000;
|
||||
parent.pulseOp = 0.3 + Math.sin(time * 2.5) * 0.15;
|
||||
parent.pulseSc = 1.02 + Math.cos(time * 3.0) * 0.02;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Solid pulsing background ring
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width + 40
|
||||
height: width
|
||||
radius: width / 2
|
||||
color: window.activeMute ? window.red : window.tabColor
|
||||
opacity: window.activeMute ? 0.3 : 0.15
|
||||
z: -1
|
||||
Behavior on color { ColorAnimation { duration: 300 } }
|
||||
|
||||
SequentialAnimation on scale {
|
||||
loops: Animation.Infinite; running: true
|
||||
NumberAnimation { to: masterOrbMa.containsMouse ? 1.15 : 1.1; duration: masterOrbMa.containsMouse ? 800 : 2000; easing.type: Easing.InOutSine }
|
||||
NumberAnimation { to: 1.0; duration: masterOrbMa.containsMouse ? 800 : 2000; easing.type: Easing.InOutSine }
|
||||
}
|
||||
}
|
||||
|
||||
// Core Shadow
|
||||
MultiEffect {
|
||||
source: centralCore
|
||||
anchors.fill: centralCore
|
||||
shadowEnabled: true
|
||||
shadowColor: "#000000"
|
||||
shadowOpacity: 0.5
|
||||
shadowBlur: 1.2
|
||||
shadowVerticalOffset: 6
|
||||
z: -1
|
||||
}
|
||||
|
||||
// Core Rectangle
|
||||
Rectangle {
|
||||
id: centralCore
|
||||
anchors.fill: parent
|
||||
radius: width / 2
|
||||
color: window.base
|
||||
border.color: window.activeMute ? window.red : Qt.lighter(window.tabColor, 1.1)
|
||||
border.width: 2
|
||||
clip: true
|
||||
Behavior on border.color { ColorAnimation { duration: 300 } }
|
||||
|
||||
// Volume Wave Fill
|
||||
Canvas {
|
||||
id: orbWave
|
||||
anchors.fill: parent
|
||||
|
||||
property real wavePhase: 0.0
|
||||
NumberAnimation on wavePhase {
|
||||
running: window.activeVol > 0 && window.activeVol < 100
|
||||
loops: Animation.Infinite
|
||||
from: 0; to: Math.PI * 2; duration: 1200
|
||||
}
|
||||
onWavePhaseChanged: requestPaint()
|
||||
|
||||
Connections {
|
||||
target: window
|
||||
function onActiveVolChanged() { orbWave.requestPaint() }
|
||||
function onActiveMuteChanged() { orbWave.requestPaint() }
|
||||
function onTabColorChanged() { orbWave.requestPaint() }
|
||||
}
|
||||
|
||||
onPaint: {
|
||||
var ctx = getContext("2d");
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
if (window.activeVol <= 0) return;
|
||||
|
||||
var fillRatio = window.activeVol / 100.0;
|
||||
var r = width / 2;
|
||||
var fillY = height * (1.0 - fillRatio);
|
||||
|
||||
ctx.save();
|
||||
|
||||
// 1. Establish the circular clipping mask
|
||||
ctx.beginPath();
|
||||
ctx.arc(r, r, r, 0, 2 * Math.PI);
|
||||
ctx.clip();
|
||||
|
||||
// 2. Draw the actual wave filling
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, fillY);
|
||||
|
||||
if (fillRatio < 0.99) {
|
||||
var waveAmp = 8 * Math.sin(fillRatio * Math.PI);
|
||||
var cp1y = fillY + Math.sin(wavePhase) * waveAmp;
|
||||
var cp2y = fillY + Math.cos(wavePhase + Math.PI) * waveAmp;
|
||||
ctx.bezierCurveTo(width * 0.33, cp2y, width * 0.66, cp1y, width, fillY);
|
||||
ctx.lineTo(width, height);
|
||||
ctx.lineTo(0, height);
|
||||
} else {
|
||||
ctx.lineTo(width, 0);
|
||||
ctx.lineTo(width, height);
|
||||
ctx.lineTo(0, height);
|
||||
}
|
||||
ctx.closePath();
|
||||
|
||||
// Vibrant gradient matching the network orb
|
||||
var grad = ctx.createLinearGradient(0, 0, 0, height);
|
||||
if (window.activeMute) {
|
||||
grad.addColorStop(0, Qt.lighter(window.red, 1.15).toString());
|
||||
grad.addColorStop(1, window.red.toString());
|
||||
} else {
|
||||
grad.addColorStop(0, Qt.lighter(window.tabColor, 1.15).toString());
|
||||
grad.addColorStop(1, window.tabColor.toString());
|
||||
}
|
||||
ctx.fillStyle = grad;
|
||||
ctx.globalAlpha = 1.0;
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
|
||||
// Dual-Layer Text for contrast clipping
|
||||
// 1. Base Text (Visible when empty)
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
font.family: "JetBrains Mono"
|
||||
font.weight: Font.Black
|
||||
font.pixelSize: 32
|
||||
color: window.activeMute ? window.red : window.text
|
||||
text: window.activeMute ? "MUTE" : window.activeVol + "%"
|
||||
Behavior on color { ColorAnimation { duration: 200 } }
|
||||
}
|
||||
|
||||
// 2. Clipped Text (Dark text that reveals over the wave fill dynamically)
|
||||
Item {
|
||||
id: waveClipItem
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
// Calculate the exact wave offset at the center of the orb using the Bezier formula
|
||||
property real fillRatio: window.activeVol / 100.0
|
||||
property real waveAmp: fillRatio < 0.99 ? 8 * Math.sin(fillRatio * Math.PI) : 0
|
||||
property real waveCenterOffset: 0.375 * waveAmp * (Math.sin(orbWave.wavePhase) - Math.cos(orbWave.wavePhase))
|
||||
property real baseClipHeight: parent.height * fillRatio
|
||||
|
||||
height: Math.min(parent.height, Math.max(0, baseClipHeight - waveCenterOffset))
|
||||
clip: true
|
||||
visible: window.activeVol > 0
|
||||
|
||||
Text {
|
||||
x: waveClipItem.width / 2 - width / 2
|
||||
y: (centralCore.height / 2) - (height / 2) - (centralCore.height - waveClipItem.height)
|
||||
font.family: "JetBrains Mono"
|
||||
font.weight: Font.Black
|
||||
font.pixelSize: 32
|
||||
color: window.crust
|
||||
text: window.activeMute ? "MUTE" : window.activeVol + "%"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: masterOrbMa
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
let type = window.activeTab === "inputs" ? "source" : "sink";
|
||||
Quickshell.execDetached(["bash", window.scriptsDir + "/audio_control.sh", "toggle-mute", type, window.activeId]);
|
||||
audioPoller.running = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Details & Slider
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
spacing: 10
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 2
|
||||
Text {
|
||||
Layout.fillWidth: true; elide: Text.ElideRight
|
||||
font.family: "JetBrains Mono"; font.weight: Font.Black; font.pixelSize: 20
|
||||
color: window.text
|
||||
text: window.activeName
|
||||
}
|
||||
Text {
|
||||
Layout.fillWidth: true; elide: Text.ElideRight
|
||||
font.family: "JetBrains Mono"; font.pixelSize: 13
|
||||
color: window.subtext0
|
||||
text: window.activeTab === "apps" ? "Master Output Volume" : window.activeDesc
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.fillHeight: true } // spacer
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 15
|
||||
|
||||
// Slider
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
height: 24
|
||||
|
||||
Timer {
|
||||
id: masterCmdThrottle
|
||||
interval: 50
|
||||
property int targetPct: -1
|
||||
onTriggered: {
|
||||
if (targetPct >= 0) {
|
||||
let type = window.activeTab === "inputs" ? "source" : "sink";
|
||||
if (targetPct > 0 && window.activeMute) {
|
||||
Quickshell.execDetached(["bash", window.scriptsDir + "/audio_control.sh", "toggle-mute", type, window.activeId]);
|
||||
}
|
||||
Quickshell.execDetached(["bash", window.scriptsDir + "/audio_control.sh", "set-volume", type, window.activeId, targetPct]);
|
||||
targetPct = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent; radius: 12
|
||||
color: "#0dffffff"; border.color: "#1affffff"; border.width: 1
|
||||
clip: true
|
||||
|
||||
Rectangle {
|
||||
height: parent.height
|
||||
width: parent.width * (Math.min(100, window.activeVol) / 100)
|
||||
radius: 12
|
||||
opacity: window.activeMute ? 0.3 : (masterSliderMa.containsMouse ? 1.0 : 0.85)
|
||||
Behavior on opacity { NumberAnimation { duration: 200 } }
|
||||
Behavior on width { enabled: !window.draggingMaster; NumberAnimation { duration: 300; easing.type: Easing.OutQuint } }
|
||||
|
||||
gradient: Gradient {
|
||||
orientation: Gradient.Horizontal
|
||||
GradientStop { position: 0.0; color: window.activeMute ? window.surface2 : window.tabColor; Behavior on color { ColorAnimation{duration: 300} } }
|
||||
GradientStop { position: 1.0; color: window.activeMute ? Qt.lighter(window.surface2, 1.15) : Qt.lighter(window.tabColor, 1.25); Behavior on color { ColorAnimation{duration: 300} } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: masterSliderMa
|
||||
anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor
|
||||
onPressed: (mouse) => { syncDelay.stop(); window.draggingMaster = true; updateVol(mouse.x); }
|
||||
onPositionChanged: (mouse) => { if (pressed) updateVol(mouse.x); }
|
||||
onReleased: { syncDelay.restart(); audioPoller.running = true; }
|
||||
|
||||
function updateVol(mx) {
|
||||
let pct = Math.max(0, Math.min(100, Math.round((mx / width) * 100)));
|
||||
window.activeVol = pct; // Instant visual feedback on orb
|
||||
|
||||
masterCmdThrottle.targetPct = pct;
|
||||
if (!masterCmdThrottle.running) masterCmdThrottle.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// TABS
|
||||
// ==========================================
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 54
|
||||
radius: 14
|
||||
color: "#0dffffff"
|
||||
border.color: "#1affffff"
|
||||
border.width: 1
|
||||
opacity: introHeader
|
||||
transform: Translate { y: 20 * (1.0 - introHeader) }
|
||||
|
||||
Rectangle {
|
||||
width: (parent.width - 2) / 3
|
||||
height: parent.height - 2
|
||||
y: 1
|
||||
radius: 10
|
||||
x: {
|
||||
if (window.activeTab === "outputs") return 1;
|
||||
if (window.activeTab === "inputs") return width + 1;
|
||||
return (width * 2) + 1;
|
||||
}
|
||||
Behavior on x { NumberAnimation { duration: 500; easing.type: Easing.OutBack; easing.overshoot: 1.1 } }
|
||||
|
||||
gradient: Gradient {
|
||||
orientation: Gradient.Horizontal
|
||||
GradientStop { position: 0.0; color: window.tabColor; Behavior on color { ColorAnimation { duration: 400 } } }
|
||||
GradientStop { position: 1.0; color: Qt.lighter(window.tabColor, 1.15); Behavior on color { ColorAnimation { duration: 400 } } }
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
Repeater {
|
||||
model: ListModel {
|
||||
ListElement { tabId: "outputs"; icon: ""; label: "Outputs" }
|
||||
ListElement { tabId: "inputs"; icon: ""; label: "Inputs" }
|
||||
ListElement { tabId: "apps"; icon: ""; label: "Streams" }
|
||||
}
|
||||
|
||||
delegate: Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
RowLayout {
|
||||
anchors.centerIn: parent
|
||||
spacing: 8
|
||||
Text {
|
||||
font.family: "Iosevka Nerd Font"; font.pixelSize: 18
|
||||
color: window.activeTab === tabId ? window.crust : (tabMa.containsMouse ? window.text : window.subtext0)
|
||||
text: icon
|
||||
Behavior on color { ColorAnimation { duration: 200 } }
|
||||
}
|
||||
Text {
|
||||
font.family: "JetBrains Mono"; font.weight: Font.Black; font.pixelSize: 13
|
||||
color: window.activeTab === tabId ? window.crust : (tabMa.containsMouse ? window.text : window.subtext0)
|
||||
text: label
|
||||
Behavior on color { ColorAnimation { duration: 200 } }
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: tabMa
|
||||
anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
window.activeTab = tabId;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// LIST VIEW CONTENT
|
||||
// ==========================================
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
opacity: introContent
|
||||
transform: Translate { y: 20 * (1.0 - introContent) }
|
||||
|
||||
ListView {
|
||||
id: contentList
|
||||
anchors.fill: parent
|
||||
spacing: 12
|
||||
clip: true
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
|
||||
// Elegant sliding transitions when models rearrange
|
||||
add: Transition {
|
||||
NumberAnimation { property: "opacity"; from: 0; to: 1; duration: 400; easing.type: Easing.OutQuint }
|
||||
NumberAnimation { property: "scale"; from: 0.9; to: 1; duration: 400; easing.type: Easing.OutBack }
|
||||
}
|
||||
displaced: Transition {
|
||||
SpringAnimation { property: "y"; spring: 3; damping: 0.2; mass: 0.2 }
|
||||
}
|
||||
|
||||
model: {
|
||||
if (window.activeTab === "outputs") return outputsModel;
|
||||
if (window.activeTab === "inputs") return inputsModel;
|
||||
return appsModel;
|
||||
}
|
||||
|
||||
Item {
|
||||
width: contentList.width; height: contentList.height
|
||||
visible: contentList.count === 0
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
spacing: 10
|
||||
Text { Layout.alignment: Qt.AlignHCenter; font.family: "Iosevka Nerd Font"; font.pixelSize: 32; color: window.surface2; text: "" }
|
||||
Text { Layout.alignment: Qt.AlignHCenter; font.family: "JetBrains Mono"; font.pixelSize: 14; color: window.overlay0; text: "No active streams" }
|
||||
}
|
||||
}
|
||||
|
||||
delegate: Rectangle {
|
||||
id: delegateRoot
|
||||
width: contentList.width
|
||||
|
||||
// Staggered Intro Animation Timer
|
||||
property bool isLoaded: false
|
||||
Timer {
|
||||
running: true
|
||||
interval: 40 + (index * 40)
|
||||
onTriggered: delegateRoot.isLoaded = true
|
||||
}
|
||||
|
||||
// Intro transforms
|
||||
opacity: isLoaded ? 1.0 : 0.0
|
||||
transform: Translate { y: isLoaded ? 0 : 15 }
|
||||
Behavior on opacity { NumberAnimation { duration: 500; easing.type: Easing.OutQuint } }
|
||||
Behavior on transform { NumberAnimation { duration: 500; easing.type: Easing.OutQuint } }
|
||||
|
||||
// Dynamic Height: The active hero element collapses its bottom slider row
|
||||
property bool isActiveNode: model.is_default && window.activeTab !== "apps"
|
||||
height: isActiveNode ? 60 : 100
|
||||
Behavior on height { NumberAnimation { duration: 400; easing.type: Easing.OutQuint } }
|
||||
|
||||
radius: 14
|
||||
|
||||
property bool isHovered: cardMa.containsMouse && !isActiveNode
|
||||
|
||||
color: isActiveNode ? window.tabColor : (isHovered ? "#0affffff" : "#05ffffff")
|
||||
border.color: isActiveNode ? window.tabColor : "#1affffff"
|
||||
border.width: isActiveNode ? 2 : 1
|
||||
Behavior on border.color { ColorAnimation { duration: 300 } }
|
||||
Behavior on color { ColorAnimation { duration: 300 } }
|
||||
|
||||
// Full card selection listener
|
||||
MouseArea {
|
||||
id: cardMa
|
||||
anchors.fill: parent
|
||||
hoverEnabled: window.activeTab !== "apps"
|
||||
cursorShape: window.activeTab !== "apps" ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: {
|
||||
if (window.activeTab !== "apps" && !model.is_default) {
|
||||
let type = window.activeTab === "outputs" ? "sink" : "source";
|
||||
Quickshell.execDetached(["bash", window.scriptsDir + "/audio_control.sh", "set-default", type, model.name]);
|
||||
audioPoller.running = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: 16
|
||||
anchors.rightMargin: 16
|
||||
anchors.topMargin: 12
|
||||
anchors.bottomMargin: isActiveNode ? 12 : 16 // Prevent slider crowding bottom bounds
|
||||
spacing: 12
|
||||
|
||||
// Top row: Text info and Icon
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 12
|
||||
|
||||
Text {
|
||||
font.family: "Iosevka Nerd Font"; font.pixelSize: 22
|
||||
color: isActiveNode ? window.crust : window.text
|
||||
Behavior on color { ColorAnimation { duration: 200 } }
|
||||
text: {
|
||||
if (window.activeTab === "inputs") return "";
|
||||
if (window.activeTab === "apps") return "";
|
||||
if (model.description.toLowerCase().indexOf("headset") !== -1 || model.description.toLowerCase().indexOf("headphones") !== -1) return "";
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 2
|
||||
Text {
|
||||
Layout.fillWidth: true; elide: Text.ElideRight
|
||||
font.family: "JetBrains Mono"; font.weight: Font.Bold; font.pixelSize: 14
|
||||
color: isActiveNode ? window.crust : window.text
|
||||
text: model.description
|
||||
}
|
||||
Text {
|
||||
Layout.fillWidth: true; elide: Text.ElideRight
|
||||
font.family: "JetBrains Mono"; font.pixelSize: 11
|
||||
color: isActiveNode ? Qt.darker(window.crust, 1.5) : window.subtext0
|
||||
text: isActiveNode ? "Active Default" : model.name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom row: Custom Slider & Mute (Hides if it's the active node)
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 15
|
||||
visible: !isActiveNode
|
||||
opacity: isActiveNode ? 0.0 : 1.0
|
||||
Behavior on opacity { NumberAnimation { duration: 200 } }
|
||||
|
||||
Rectangle {
|
||||
Layout.preferredWidth: 32; Layout.preferredHeight: 32; radius: 16
|
||||
color: muteMa.containsMouse ? "#1affffff" : "transparent"
|
||||
border.color: muteMa.containsMouse ? (model.mute ? window.overlay0 : window.tabColor) : "transparent"
|
||||
Behavior on color { ColorAnimation { duration: 150 } }
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
font.family: "Iosevka Nerd Font"; font.pixelSize: 18
|
||||
color: model.mute ? window.overlay0 : window.subtext0
|
||||
text: model.mute || model.volume === 0 ? "" : (model.volume > 50 ? "" : "")
|
||||
Behavior on color { ColorAnimation { duration: 200 } }
|
||||
}
|
||||
MouseArea {
|
||||
id: muteMa
|
||||
anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
let type = "sink";
|
||||
if (window.activeTab === "inputs") type = "source";
|
||||
if (window.activeTab === "apps") type = "sink-input";
|
||||
Quickshell.execDetached(["bash", window.scriptsDir + "/audio_control.sh", "toggle-mute", type, model.id]);
|
||||
audioPoller.running = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Local Slider
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
height: 14 // Slightly thinner than master slider for hierarchy
|
||||
|
||||
Timer {
|
||||
id: volCmdThrottle
|
||||
interval: 50
|
||||
property int targetPct: -1
|
||||
onTriggered: {
|
||||
if (targetPct >= 0) {
|
||||
let type = "sink";
|
||||
if (window.activeTab === "inputs") type = "source";
|
||||
if (window.activeTab === "apps") type = "sink-input";
|
||||
|
||||
if (targetPct > 0 && model.mute) {
|
||||
Quickshell.execDetached(["bash", window.scriptsDir + "/audio_control.sh", "toggle-mute", type, model.id]);
|
||||
}
|
||||
Quickshell.execDetached(["bash", window.scriptsDir + "/audio_control.sh", "set-volume", type, model.id, targetPct]);
|
||||
targetPct = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent; radius: 7
|
||||
color: "#0dffffff"; border.color: "#1affffff"; border.width: 1
|
||||
clip: true
|
||||
|
||||
Rectangle {
|
||||
height: parent.height
|
||||
width: parent.width * (Math.min(100, model.volume) / 100)
|
||||
radius: 7
|
||||
|
||||
// Heavily dimmed if muted, slightly dimmed if background node
|
||||
opacity: model.mute ? 0.3 : (volSliderMa.containsMouse ? 0.7 : 0.4)
|
||||
Behavior on opacity { NumberAnimation { duration: 200 } }
|
||||
Behavior on width { enabled: !window.draggingNodes[model.id]; NumberAnimation { duration: 300; easing.type: Easing.OutQuint } }
|
||||
|
||||
gradient: Gradient {
|
||||
orientation: Gradient.Horizontal
|
||||
GradientStop { position: 0.0; color: model.mute ? window.surface2 : window.tabColor; Behavior on color { ColorAnimation { duration: 300 } } }
|
||||
GradientStop { position: 1.0; color: model.mute ? Qt.lighter(window.surface2, 1.15) : Qt.lighter(window.tabColor, 1.25); Behavior on color { ColorAnimation { duration: 300 } } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: volSliderMa
|
||||
anchors.fill: parent; hoverEnabled: true; cursorShape: Qt.PointingHandCursor
|
||||
onPressed: (mouse) => { syncDelay.stop(); window.draggingNodes[model.id] = true; updateVol(mouse.x); }
|
||||
onPositionChanged: (mouse) => { if (pressed) updateVol(mouse.x); }
|
||||
onReleased: { syncDelay.restart(); audioPoller.running = true; }
|
||||
|
||||
function updateVol(mx) {
|
||||
let pct = Math.max(0, Math.min(100, Math.round((mx / width) * 100)));
|
||||
|
||||
let targetList = window.activeTab === "outputs" ? outputsModel : (window.activeTab === "inputs" ? inputsModel : appsModel);
|
||||
for (let i = 0; i < targetList.count; i++) {
|
||||
if (targetList.get(i).id === model.id) {
|
||||
targetList.setProperty(i, "volume", pct);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
volCmdThrottle.targetPct = pct;
|
||||
if (!volCmdThrottle.running) volCmdThrottle.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.preferredWidth: 35
|
||||
font.family: "JetBrains Mono"; font.weight: Font.Bold; font.pixelSize: 12
|
||||
color: window.subtext0
|
||||
text: model.volume + "%"
|
||||
horizontalAlignment: Text.AlignRight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
|
||||
FloatingWindow {
|
||||
id: root
|
||||
title: "qs-volume"
|
||||
objectName: "quickshellWidget"
|
||||
color: "transparent"
|
||||
visible: true
|
||||
|
||||
implicitWidth: 480
|
||||
implicitHeight: volumePopup.implicitHeight
|
||||
|
||||
VolumePopup {
|
||||
id: volumePopup
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: "Escape"
|
||||
onActivated: Qt.quit()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
ACTION=$1
|
||||
TYPE=$2
|
||||
ID=$3
|
||||
VAL=$4
|
||||
|
||||
case $ACTION in
|
||||
set-volume)
|
||||
# Type should be 'sink', 'source', or 'sink-input'
|
||||
pactl set-$TYPE-volume "$ID" "$VAL%"
|
||||
;;
|
||||
toggle-mute)
|
||||
pactl set-$TYPE-mute "$ID" toggle
|
||||
;;
|
||||
set-default)
|
||||
# For setting defaults, we need to use the name rather than the index
|
||||
pactl set-default-$TYPE "$ID"
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env python3
|
||||
import subprocess
|
||||
import json
|
||||
import sys
|
||||
|
||||
def run_cmd(cmd):
|
||||
try:
|
||||
return subprocess.check_output(cmd, shell=True, stderr=subprocess.DEVNULL).decode('utf-8')
|
||||
except:
|
||||
return "[]"
|
||||
|
||||
def parse_pactl(output):
|
||||
try:
|
||||
return json.loads(output)
|
||||
except:
|
||||
return []
|
||||
|
||||
def get_valid_string(*args):
|
||||
"""Safely return the first valid string that isn't 'null' or empty."""
|
||||
for arg in args:
|
||||
if arg and str(arg).strip().lower() not in ["null", "none", ""]:
|
||||
return str(arg)
|
||||
return ""
|
||||
|
||||
def get_data():
|
||||
sinks = parse_pactl(run_cmd("pactl -f json list sinks"))
|
||||
sources = parse_pactl(run_cmd("pactl -f json list sources"))
|
||||
sink_inputs = parse_pactl(run_cmd("pactl -f json list sink-inputs"))
|
||||
|
||||
# Get defaults
|
||||
try:
|
||||
info = parse_pactl(run_cmd("pactl -f json info"))
|
||||
default_sink = info.get("default_sink_name", "")
|
||||
default_source = info.get("default_source_name", "")
|
||||
except:
|
||||
default_sink = ""
|
||||
default_source = ""
|
||||
|
||||
def format_node(n, is_default=False, is_app=False):
|
||||
# Extract volume gracefully
|
||||
vol = 0
|
||||
if "volume" in n and isinstance(n["volume"], dict):
|
||||
if "front-left" in n["volume"]:
|
||||
vol = int(n["volume"]["front-left"].get("value_percent", "0%").strip("%"))
|
||||
elif "mono" in n["volume"]:
|
||||
vol = int(n["volume"]["mono"].get("value_percent", "0%").strip("%"))
|
||||
|
||||
props = n.get("properties", {})
|
||||
|
||||
if is_app:
|
||||
display_name = get_valid_string(props.get("application.name"), props.get("application.process.binary"), "Unknown App")
|
||||
sub_desc = get_valid_string(props.get("media.name"), props.get("window.title"), props.get("media.role"), "Audio Stream")
|
||||
else:
|
||||
display_name = get_valid_string(props.get("device.description"), n.get("name"), "Unknown Device")
|
||||
sub_desc = get_valid_string(n.get("name"), "Unknown")
|
||||
|
||||
icon = get_valid_string(props.get("application.icon_name"), props.get("device.icon_name"), "audio-card")
|
||||
|
||||
return {
|
||||
"id": str(n.get("index")),
|
||||
"name": sub_desc,
|
||||
"description": display_name,
|
||||
"volume": vol,
|
||||
"mute": bool(n.get("mute", False)),
|
||||
"is_default": bool(is_default),
|
||||
"icon": icon
|
||||
}
|
||||
|
||||
# Filter out empty apps/system sounds
|
||||
apps = []
|
||||
for s in sink_inputs:
|
||||
props = s.get("properties", {})
|
||||
if props.get("application.id") != "org.PulseAudio.pavucontrol":
|
||||
apps.append(format_node(s, is_app=True))
|
||||
|
||||
out = {
|
||||
"outputs": [format_node(s, s.get("name") == default_sink) for s in sinks],
|
||||
"inputs": [format_node(s, s.get("name") == default_source) for s in sources],
|
||||
"apps": apps
|
||||
}
|
||||
|
||||
print(json.dumps(out))
|
||||
|
||||
if __name__ == "__main__":
|
||||
get_data()
|
||||
@@ -79,8 +79,8 @@
|
||||
]
|
||||
},
|
||||
"scroll-step": 5,
|
||||
"on-click": "pamixer -t",
|
||||
"on-click-right": "pavucontrol"
|
||||
"on-click": "quickshell -p ~/.config/quickshell/audio/audioWidget.qml > /dev/null 2>&1",
|
||||
"on-click-right": "pamixer -t"
|
||||
},
|
||||
|
||||
"tray": {
|
||||
|
||||
Reference in New Issue
Block a user