Compare commits
15 Commits
4bc368dfd9
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 37e8efa795 | |||
| 6847a123d5 | |||
| f28788b71a | |||
| 5aec12cd1f | |||
| 6efa5d599c | |||
| 9248f9b33f | |||
| 252c88b869 | |||
| 63f99ff01a | |||
| 586e4b6320 | |||
| c2b28df404 | |||
| 8d95eeb892 | |||
| d0d3fc23e8 | |||
| 98235a8c18 | |||
| e645ea1641 | |||
| 7824d27555 |
@ -11,6 +11,7 @@ default_roles:
|
||||
# - ssh
|
||||
- hyprland
|
||||
- ghostty
|
||||
- quickshell
|
||||
|
||||
temp:
|
||||
- docker # Container platform
|
||||
@ -67,4 +68,5 @@ aur_packages:
|
||||
- pgformatter-git
|
||||
# - jetbrains-toolbox
|
||||
|
||||
home_dir: "/home/johannes"
|
||||
config_dir: "/home/johannes/.config"
|
||||
|
||||
@ -2,7 +2,8 @@
|
||||
- name: Setup dotfiles configuration
|
||||
hosts: localhost
|
||||
connection: local
|
||||
become: false
|
||||
become: true
|
||||
become_user: johannes
|
||||
vars:
|
||||
dotfiles_dir: "{{ ansible_env.HOME }}/dotfiles"
|
||||
# roles:
|
||||
|
||||
@ -14,7 +14,7 @@ source = ./workspaces.conf
|
||||
# See https://wiki.hypr.land/Configuring/Keywords/
|
||||
|
||||
$terminal = ghostty
|
||||
$fileManager = thunar
|
||||
$fileManager = ghostty -e yazi
|
||||
$menu = env XDG_CURRENT_DESKTOP=Hyprland wofi --show drun
|
||||
|
||||
#################
|
||||
@ -25,7 +25,8 @@ $menu = env XDG_CURRENT_DESKTOP=Hyprland wofi --show drun
|
||||
# Or execute your favorite apps at launch like this:
|
||||
# exec-once = waybar
|
||||
exec-once = /usr/lib/hyprpolkitagent/hyprpolkitagent
|
||||
exec-once = hyprpanel
|
||||
# exec-once = hyprpanel
|
||||
exec-once = quickshell
|
||||
exec-once = dunst
|
||||
exec-once = firefox
|
||||
exec-once = hyprpaper
|
||||
@ -45,7 +46,7 @@ debug:disable_logs = false
|
||||
# See https://wiki.hypr.land/Configuring/Environment-variables/
|
||||
env = XCURSOR_SIZE,24
|
||||
env = HYPRCURSOR_SIZE,24
|
||||
env = QT_QPA_PLATFORM,waylandl;xcb
|
||||
env = QT_QPA_PLATFORM,wayland
|
||||
env = QT_QPA_PLATFORMTHEME,qt5ct
|
||||
env = QT_WAYLAND_DISABLE_WINDOWDECORATION,1
|
||||
env = QT_AUTO_SCREEN_SCALE_FACTOR,1
|
||||
@ -90,18 +91,13 @@ env = _JAVA_AWT_WM_NONREPARENTING, 1
|
||||
# https://wiki.hypr.land/Configuring/Variables/#general
|
||||
general {
|
||||
gaps_in = 5
|
||||
gaps_out = 20
|
||||
gaps_out = 10
|
||||
|
||||
border_size = 2
|
||||
border_size = 3
|
||||
|
||||
# https://wiki.hypr.land/Configuring/Variables/#variable-types for info about colors
|
||||
col.active_border = rgba(33ccffee) rgba(00ff99ee) 45deg
|
||||
col.active_border = rgba(9b1a1aee) rgba(c94040ee) 45deg
|
||||
col.inactive_border = rgba(595959aa)
|
||||
# Active: cosmic purple -> neon cyan gradient
|
||||
# col.active_border = rgba(7f5af0ee) rgba(00d1ffee) 45deg
|
||||
#
|
||||
# # Inactive: deep space slate
|
||||
# col.inactive_border = rgba(0c0e14aa)
|
||||
|
||||
# Set to true enable resizing windows by clicking and dragging on borders and gaps
|
||||
resize_on_border = false
|
||||
@ -200,7 +196,7 @@ animations {
|
||||
|
||||
# See https://wiki.hypr.land/Configuring/Dwindle-Layout/ for more
|
||||
dwindle {
|
||||
pseudotile = true # Master switch for pseudotiling. Enabling is bound to mainMod + P in the keybinds section below
|
||||
# pseudotile = true # Master switch for pseudotiling. Enabling is bound to mainMod + P in the keybinds section below
|
||||
preserve_split = true # You probably want this
|
||||
}
|
||||
|
||||
@ -334,6 +330,13 @@ windowrule {
|
||||
suppress_event = maximize
|
||||
}
|
||||
|
||||
xwayland {
|
||||
enabled = true
|
||||
use_nearest_neighbor = true
|
||||
force_zero_scaling = false
|
||||
create_abstract_socket = false
|
||||
}
|
||||
|
||||
# Fix some dragging issues with XWayland
|
||||
windowrule {
|
||||
name = fix-xwayland-drags
|
||||
|
||||
@ -1,23 +1,23 @@
|
||||
preload = /home/johannes/Pictures/Wallpapers/wallhaven-5g22q5.png
|
||||
preload = /home/johannes/Pictures/Wallpapers/japan-artistic-3840x2160-25406.jpg
|
||||
|
||||
wallpaper {
|
||||
monitor = DP-1
|
||||
path =/home/johannes/Pictures/Wallpapers/wallhaven-5g22q5.png
|
||||
path =/home/johannes/Pictures/Wallpapers/japan-artistic-3840x2160-25406.jpg
|
||||
# fitmode = cover
|
||||
}
|
||||
wallpaper {
|
||||
monitor = DP-2
|
||||
path =/home/johannes/Pictures/Wallpapers/wallhaven-5g22q5.png
|
||||
path =/home/johannes/Pictures/Wallpapers/japan-artistic-3840x2160-25406.jpg
|
||||
# fitmode = cover
|
||||
}
|
||||
wallpaper {
|
||||
monitor = HDMI-A-1
|
||||
path =/home/johannes/Pictures/Wallpapers/wallhaven-5g22q5.png
|
||||
path =/home/johannes/Pictures/Wallpapers/japan-artistic-3840x2160-25406.jpg
|
||||
# fitmode = cover
|
||||
}
|
||||
wallpaper {
|
||||
monitor = HDMI-A-2
|
||||
path =/home/johannes/Pictures/Wallpapers/wallhaven-5g22q5.png
|
||||
path =/home/johannes/Pictures/Wallpapers/japan-artistic-3840x2160-25406.jpg
|
||||
# fitmode = cover
|
||||
}
|
||||
|
||||
|
||||
3
roles/hyprland/files/hypr/monitors_jk-nb.conf
Normal file
3
roles/hyprland/files/hypr/monitors_jk-nb.conf
Normal file
@ -0,0 +1,3 @@
|
||||
# Generated by nwg-displays on 2026-04-10 at 16:05:43. Do not edit manually.
|
||||
|
||||
monitor=eDP-1,2880x1800@90.0,0x0,1.4999999999999996
|
||||
@ -1,7 +1,7 @@
|
||||
# Generated by nwg-displays on 2026-02-28 at 11:47:04. Do not edit manually.
|
||||
# Generated by nwg-displays on 2026-04-20 at 01:17:12. Do not edit manually.
|
||||
|
||||
monitor=DP-1,2560x1440@165.0,6626x876,1.0
|
||||
monitor=DP-2,2560x1440@165.0,4066x876,1.0
|
||||
monitor=HDMI-A-1,disable
|
||||
monitor=HDMI-A-2,1920x1080@60.0,9186x396,1.0
|
||||
monitor=HDMI-A-2,transform,3
|
||||
monitor=HDMI-A-1,disable
|
||||
|
||||
@ -41,5 +41,8 @@
|
||||
"bar.battery.label": false,
|
||||
"bar.battery.hideLabelWhenFull": true,
|
||||
"theme.bar.buttons.battery.enableBorder": false,
|
||||
"theme.osd.enable": true
|
||||
"theme.osd.enable": true,
|
||||
"theme.bar.buttons.y_margins": "0.2em",
|
||||
"theme.bar.dropdownGap": "2.9em",
|
||||
"theme.bar.outer_spacing": "1.6em"
|
||||
}
|
||||
@ -15,32 +15,31 @@
|
||||
path: "{{ config_dir }}/hypr"
|
||||
state: directory
|
||||
mode: '0755'
|
||||
owner: johannes
|
||||
group: johannes
|
||||
|
||||
- name: Symlink hyprland config files
|
||||
file:
|
||||
src: "{{ role_path }}/files/hypr/{{ item }}"
|
||||
dest: "{{ config_dir }}/hypr/{{ item }}"
|
||||
src: "{{ role_path }}/files/hypr/{{ conf_file }}"
|
||||
dest: "{{ config_dir }}/hypr/{{ conf_file }}"
|
||||
state: link
|
||||
force: true
|
||||
loop:
|
||||
- hyprland.conf
|
||||
- hyprlock.conf
|
||||
- hyprpaper.conf
|
||||
# - workspaces.conf
|
||||
- scripts
|
||||
loop_control:
|
||||
loop_var: conf_file
|
||||
|
||||
- name: Symlink host-specific monitors.conf
|
||||
file:
|
||||
src: "{{ role_path }}/files/hypr/monitors_{{ ansible_hostname }}.conf"
|
||||
src: "{{ role_path }}/files/hypr/monitors_{{ ansible_facts['hostname'] }}.conf"
|
||||
dest: "{{ config_dir }}/hypr/monitors.conf"
|
||||
state: link
|
||||
force: true
|
||||
|
||||
- name: Symlink host-specific workspaces.conf
|
||||
file:
|
||||
src: "{{ role_path }}/files/hypr/workspaces_{{ ansible_hostname }}.conf"
|
||||
src: "{{ role_path }}/files/hypr/workspaces_{{ ansible_facts['hostname'] }}.conf"
|
||||
dest: "{{ config_dir }}/hypr/workspaces.conf"
|
||||
state: link
|
||||
force: true
|
||||
|
||||
@ -1 +1 @@
|
||||
vim.opt_local.shiftwidth = 4
|
||||
vim.opt_local.shiftwidth = 2
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"LuaSnip": { "branch": "master", "commit": "5a1e39223db9a0498024a77b8441169d260c8c25" },
|
||||
"LuaSnip": { "branch": "master", "commit": "642b0c595e11608b4c18219e93b88d7637af27bc" },
|
||||
"alpha-nvim": { "branch": "main", "commit": "a9d8fb72213c8b461e791409e7feabb74eb6ce73" },
|
||||
"auto-session": { "branch": "main", "commit": "62437532b38495551410b3f377bcf4aaac574ebe" },
|
||||
"bufdelete.nvim": { "branch": "master", "commit": "f6bcea78afb3060b198125256f897040538bcb81" },
|
||||
@ -12,28 +12,29 @@
|
||||
"dressing.nvim": { "branch": "master", "commit": "2d7c2db2507fa3c4956142ee607431ddb2828639" },
|
||||
"flutter-tools.nvim": { "branch": "main", "commit": "677cc07c16e8b89999108d2ebeefcfc5f539b73c" },
|
||||
"friendly-snippets": { "branch": "main", "commit": "6cd7280adead7f586db6fccbd15d2cac7e2188b9" },
|
||||
"lazy.nvim": { "branch": "main", "commit": "306a05526ada86a7b30af95c5cc81ffba93fef97" },
|
||||
"lazydev.nvim": { "branch": "main", "commit": "ff2cbcba459b637ec3fd165a2be59b7bbaeedf0d" },
|
||||
"lazygit.nvim": { "branch": "main", "commit": "a04ad0dbc725134edbee3a5eea29290976695357" },
|
||||
"lspkind.nvim": { "branch": "master", "commit": "c7274c48137396526b59d86232eabcdc7fed8a32" },
|
||||
"lualine.nvim": { "branch": "master", "commit": "47f91c416daef12db467145e16bed5bbfe00add8" },
|
||||
"mason-lspconfig.nvim": { "branch": "main", "commit": "a676ab7282da8d651e175118bcf54483ca11e46d" },
|
||||
"lualine.nvim": { "branch": "master", "commit": "8811f3f3f4dc09d740c67e9ce399e7a541e2e5b2" },
|
||||
"mason-lspconfig.nvim": { "branch": "main", "commit": "63a3c6a80538de1003373a619e29aeda27809ad3" },
|
||||
"mason-tool-installer.nvim": { "branch": "main", "commit": "443f1ef8b5e6bf47045cb2217b6f748a223cf7dc" },
|
||||
"mason.nvim": { "branch": "main", "commit": "44d1e90e1f66e077268191e3ee9d2ac97cc18e65" },
|
||||
"monokai-nightasty.nvim": { "branch": "main", "commit": "8be5e1c6e1d59873505e81b161e923264dfa5c1a" },
|
||||
"none-ls-extras.nvim": { "branch": "main", "commit": "c6fa39ac52814182c05552cb5d3750cae23ff0f0" },
|
||||
"none-ls.nvim": { "branch": "main", "commit": "c9317c2a8629d4e39e7cf47be74cb67f3ab37cda" },
|
||||
"mason.nvim": { "branch": "main", "commit": "b03fb0f20bc1d43daf558cda981a2be22e73ac42" },
|
||||
"monokai-nightasty.nvim": { "branch": "main", "commit": "1e9b92006782a1217d0a7a871b871768f1cbf5ed" },
|
||||
"none-ls-extras.nvim": { "branch": "main", "commit": "14fa31ce8c0268a3b2c9cc14979ecf771982d433" },
|
||||
"none-ls.nvim": { "branch": "main", "commit": "7f9301e416533b5d74e2fb3b1ce5059eeaed748b" },
|
||||
"nvim-autopairs": { "branch": "master", "commit": "59bce2eef357189c3305e25bc6dd2d138c1683f5" },
|
||||
"nvim-cmp": { "branch": "main", "commit": "da88697d7f45d16852c6b2769dc52387d1ddc45f" },
|
||||
"nvim-cmp": { "branch": "main", "commit": "a1d504892f2bc56c2e79b65c6faded2fd21f3eca" },
|
||||
"nvim-lsp-file-operations": { "branch": "master", "commit": "b9c795d3973e8eec22706af14959bc60c579e771" },
|
||||
"nvim-lspconfig": { "branch": "master", "commit": "1d13d2b0df9a0a02904c76d7ad6810f71d404406" },
|
||||
"nvim-lspconfig": { "branch": "master", "commit": "fb5fa30626ae10f7f79f740059d3769993936ecb" },
|
||||
"nvim-surround": { "branch": "main", "commit": "61319d4bd1c5e336e197defa15bd104c51f0fb29" },
|
||||
"nvim-tree.lua": { "branch": "master", "commit": "b3772adec8db61ba9098c5624a0823a77be3a23d" },
|
||||
"nvim-treesitter": { "branch": "master", "commit": "42fc28ba918343ebfd5565147a42a26580579482" },
|
||||
"nvim-tree.lua": { "branch": "master", "commit": "509962f21ab7289d8dcd28568af539be39a8c01e" },
|
||||
"nvim-treesitter": { "branch": "main", "commit": "4916d6592ede8c07973490d9322f187e07dfefac" },
|
||||
"nvim-ts-autotag": { "branch": "main", "commit": "8e1c0a389f20bf7f5b0dd0e00306c1247bda2595" },
|
||||
"nvim-web-devicons": { "branch": "master", "commit": "d7462543c9e366c0d196c7f67a945eaaf5d99414" },
|
||||
"nvim-web-devicons": { "branch": "master", "commit": "95b7a002d5dba1a42eb58f5fac5c565a485eefd0" },
|
||||
"plenary.nvim": { "branch": "master", "commit": "b9fd5226c2f76c951fc8ed5923d85e4de065e509" },
|
||||
"telescope-fzf-native.nvim": { "branch": "main", "commit": "6fea601bd2b694c6f2ae08a6c6fab14930c60e2c" },
|
||||
"telescope.nvim": { "branch": "0.1.x", "commit": "a0bbec21143c7bc5f8bb02e0005fa0b982edc026" },
|
||||
"telescope.nvim": { "branch": "master", "commit": "7ef4d6dccb78ee71e552bbd866176762ad328afa" },
|
||||
"vim-floaterm": { "branch": "master", "commit": "0ab5eb8135dc884bc543a819ac7033c15e72a76b" },
|
||||
"which-key.nvim": { "branch": "main", "commit": "3aab2147e74890957785941f0c1ad87d0a44c15a" }
|
||||
}
|
||||
|
||||
@ -20,6 +20,10 @@ keymap.set('n', 'j', 'gj', opts)
|
||||
keymap.set('n', '<UP>', 'gk', opts)
|
||||
keymap.set('n', '<DOWN>', 'gj', opts)
|
||||
|
||||
-- bind tabswitching to Tab and Shift-Tab
|
||||
keymap.set('n', '<Tab>', ':tabnext<CR>', opts)
|
||||
keymap.set('n', '<S-Tab>', ':tabprev<CR>', opts)
|
||||
|
||||
-- COMMANDS
|
||||
|
||||
-- print current working directory
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
local opt = vim.opt
|
||||
|
||||
vim.filetype.add({ extension = { mdx = 'mdx' } })
|
||||
|
||||
-- numbers
|
||||
opt.relativenumber = true
|
||||
opt.number = true
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
local lazypath = vim.fn.stdpath('data') .. 'lazy/lazy.nvim'
|
||||
local lazypath = vim.fn.stdpath('data') .. '/lazy/lazy.nvim'
|
||||
|
||||
-- bootstrap lazy
|
||||
if not (vim.uv or vim.loop).fs_stat(lazypath) then
|
||||
|
||||
@ -59,6 +59,7 @@ return {
|
||||
keymap.set('n', '<leader>sl', '<cmd>AutoSession search<cr>', opts)
|
||||
keymap.set('n', '<leader>sr', session_restore, opts)
|
||||
keymap.set('n', '<leader>sd', '<cmd>AutoSession delete<cr>', opts)
|
||||
keymap.set('n', '<leader>qa', '<cmd>qa<cr>', opts)
|
||||
|
||||
local function named_save()
|
||||
vim.ui.input({ prompt = 'Enter Session name: ' }, function(input)
|
||||
@ -70,12 +71,14 @@ return {
|
||||
local wk = require('which-key')
|
||||
wk.add({
|
||||
{ '<leader>s', group = 'auto-session' },
|
||||
{ '<leader>q', group = 'quit' },
|
||||
{ '<leader>ss', desc = 'SessionSave' },
|
||||
{ '<leader>sS', desc = 'NamedSessionSave' },
|
||||
{ '<leader>sq', desc = 'Save and Quit' },
|
||||
{ '<leader>sl', desc = 'SessionList (SessionSearch)'},
|
||||
{ '<leader>sr', desc = 'SessionRestore'},
|
||||
{ '<leader>sd', desc = 'SessionDelete'},
|
||||
{ '<leader>qa', desc = 'Quit All'},
|
||||
})
|
||||
end,
|
||||
}
|
||||
|
||||
@ -85,7 +85,16 @@ return {
|
||||
-- Explicitly enable all mason-managed servers.
|
||||
-- This is belt-and-suspenders alongside mason-lspconfig's automatic_enable,
|
||||
-- ensuring servers start regardless of mason-lspconfig version.
|
||||
vim.lsp.enable({ 'lua_ls', 'html', 'cssls', 'bashls', 'ts_ls', 'eslint', 'tailwindcss' })
|
||||
vim.lsp.enable({ 'lua_ls', 'html', 'cssls', 'bashls', 'ts_ls', 'eslint', 'tailwindcss', 'mdx_analyzer' })
|
||||
|
||||
-- mdx_analyzer: needs typescript SDK path to find tsserverlibrary.js
|
||||
vim.lsp.config('mdx_analyzer', {
|
||||
init_options = {
|
||||
typescript = {
|
||||
tsdk = vim.fn.stdpath('data') .. '/mason/packages/typescript-language-server/node_modules/typescript/lib',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
-- hyprls: not managed by mason, enable manually
|
||||
vim.lsp.config('hyprls', {
|
||||
@ -96,5 +105,11 @@ return {
|
||||
end,
|
||||
})
|
||||
vim.lsp.enable('hyprls')
|
||||
|
||||
vim.lsp.config('qmlls', {
|
||||
cmd = { 'qmlls6' },
|
||||
})
|
||||
vim.lsp.enable('qmlls')
|
||||
|
||||
end,
|
||||
}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
return {
|
||||
'nvim-telescope/telescope.nvim',
|
||||
branch = '0.1.x',
|
||||
'nvim-telescope/telescope.nvim', -- 0.1.x dropped; master has nvim 0.12 compat (ft_to_lang removal)
|
||||
dependencies = {
|
||||
'nvim-lua/plenary.nvim',
|
||||
{ "nvim-telescope/telescope-fzf-native.nvim", build = "make" },
|
||||
|
||||
@ -1,23 +1,18 @@
|
||||
return {
|
||||
"nvim-treesitter/nvim-treesitter",
|
||||
branch = "main", -- master is frozen; main required for nvim 0.12+
|
||||
event = { "BufReadPre", "BufNewFile" },
|
||||
build = ":TSUpdate",
|
||||
dependencies = {
|
||||
"windwp/nvim-ts-autotag",
|
||||
},
|
||||
config = function()
|
||||
local treesitter = require('nvim-treesitter.configs')
|
||||
|
||||
treesitter.setup({
|
||||
require('nvim-treesitter').setup({
|
||||
auto_install = true,
|
||||
sync_install = false,
|
||||
highlight = {
|
||||
enable = true,
|
||||
additional_vim_regex_highlighting = false,
|
||||
},
|
||||
prefer_git = true, -- tarball downloads fail for some parsers on 0.12
|
||||
highlight = { enable = true },
|
||||
indent = { enable = true },
|
||||
autotag = { enable = true },
|
||||
ensure_installed = {
|
||||
install = {
|
||||
'json',
|
||||
'javascript',
|
||||
'typescript',
|
||||
@ -35,17 +30,10 @@ return {
|
||||
'python',
|
||||
'gitignore',
|
||||
'c',
|
||||
},
|
||||
ignore_install = {},
|
||||
incremental_selection = {
|
||||
enable = true,
|
||||
keymaps = {
|
||||
init_selection = '<C-Space>',
|
||||
node_incremental = '<C-Space>',
|
||||
scope_incremental = false,
|
||||
node_decremental = '<bs>',
|
||||
},
|
||||
'mdx',
|
||||
},
|
||||
})
|
||||
|
||||
require('nvim-ts-autotag').setup()
|
||||
end,
|
||||
}
|
||||
|
||||
@ -3,6 +3,8 @@
|
||||
package:
|
||||
name: neovim
|
||||
state: present
|
||||
become: yes
|
||||
become_user: root
|
||||
|
||||
- name: Symlink neovim
|
||||
file:
|
||||
|
||||
140
roles/quickshell/files/bar/Bar.qml
Normal file
140
roles/quickshell/files/bar/Bar.qml
Normal file
@ -0,0 +1,140 @@
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
import "../components"
|
||||
|
||||
PanelWindow {
|
||||
id: root
|
||||
|
||||
property var modelData
|
||||
screen: modelData
|
||||
|
||||
anchors {
|
||||
top: true
|
||||
left: true
|
||||
right: true
|
||||
}
|
||||
implicitHeight: 40
|
||||
color: Theme.bg
|
||||
|
||||
readonly property string screenName: modelData.name
|
||||
property string activePopup: ""
|
||||
|
||||
readonly property int bw: Theme.borderWidth // 2
|
||||
readonly property int pad: Theme.enclosureMargin // 3
|
||||
|
||||
// ── Bar bottom border ─────────────────────────────────────────────
|
||||
Rectangle {
|
||||
anchors { bottom: parent.bottom; left: parent.left; right: parent.right }
|
||||
height: root.bw
|
||||
color: Theme.border
|
||||
}
|
||||
|
||||
// ── Bar content ───────────────────────────────────────────────────
|
||||
Item {
|
||||
anchors {
|
||||
fill: parent
|
||||
leftMargin: 8
|
||||
rightMargin: 8
|
||||
topMargin: root.bw
|
||||
bottomMargin: root.bw
|
||||
}
|
||||
|
||||
Workspaces {
|
||||
anchors { left: parent.left; verticalCenter: parent.verticalCenter }
|
||||
screenName: root.screenName
|
||||
}
|
||||
|
||||
MusicPlayer {
|
||||
id: musicChip
|
||||
anchors { horizontalCenter: parent.horizontalCenter; verticalCenter: parent.verticalCenter }
|
||||
onClicked: root.activePopup = root.activePopup === "controls" ? "" : "controls"
|
||||
}
|
||||
|
||||
Row {
|
||||
id: rightRow
|
||||
anchors { right: parent.right; verticalCenter: parent.verticalCenter }
|
||||
spacing: 8
|
||||
|
||||
SysTray {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
barWindow: root
|
||||
}
|
||||
Battery {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
NetworkStatus {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
VolumeControl {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClickedLeft: root.activePopup = root.activePopup === "mixer" ? "" : "mixer"
|
||||
}
|
||||
Clock {
|
||||
id: clockDisp
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: root.activePopup = root.activePopup === "calendar" ? "" : "calendar"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Music controls popup ──────────────────────────────────────────
|
||||
PopoutWindow {
|
||||
popupName: "controls"
|
||||
activePopup: root.activePopup
|
||||
|
||||
anchor.window: root
|
||||
anchor.rect.y: root.implicitHeight - root.bw - Theme.radius
|
||||
anchor.rect.x: Math.round((root.width - implicitWidth) / 2)
|
||||
|
||||
implicitWidth: Math.max(musicChip.implicitWidth + 2 * root.pad,
|
||||
ctrlContent.implicitWidth + 2 * root.bw)
|
||||
implicitHeight: ctrlContent.implicitHeight + root.bw + Theme.radius
|
||||
|
||||
MusicPlayerControls {
|
||||
id: ctrlContent
|
||||
anchors { left: parent.left; right: parent.right }
|
||||
player: musicChip.player
|
||||
}
|
||||
}
|
||||
|
||||
// ── Calendar popup ────────────────────────────────────────────────
|
||||
PopoutWindow {
|
||||
popupName: "calendar"
|
||||
activePopup: root.activePopup
|
||||
|
||||
anchor.window: root
|
||||
anchor.rect.y: root.implicitHeight - root.bw - Theme.radius
|
||||
readonly property real pw: Math.max(rightRow.width + 2 * root.pad,
|
||||
calContent.implicitWidth + 2 * root.bw)
|
||||
anchor.rect.x: root.width - pw
|
||||
|
||||
implicitWidth: pw
|
||||
implicitHeight: calContent.implicitHeight + root.bw + Theme.radius
|
||||
|
||||
CalendarContent {
|
||||
id: calContent
|
||||
anchors { left: parent.left; right: parent.right }
|
||||
now: clockDisp.now
|
||||
}
|
||||
}
|
||||
|
||||
// ── Volume mixer popup ────────────────────────────────────────────
|
||||
PopoutWindow {
|
||||
popupName: "mixer"
|
||||
activePopup: root.activePopup
|
||||
|
||||
anchor.window: root
|
||||
anchor.rect.y: root.implicitHeight - root.bw - Theme.radius
|
||||
readonly property real pw: Math.max(rightRow.width + 2 * root.pad,
|
||||
mixContent.implicitWidth + 2 * root.bw)
|
||||
anchor.rect.x: root.width - pw
|
||||
|
||||
implicitWidth: pw
|
||||
implicitHeight: mixContent.implicitHeight + root.bw + Theme.radius
|
||||
|
||||
VolumeMixerContent {
|
||||
id: mixContent
|
||||
anchors { left: parent.left; right: parent.right }
|
||||
}
|
||||
}
|
||||
}
|
||||
84
roles/quickshell/files/bar/Battery.qml
Normal file
84
roles/quickshell/files/bar/Battery.qml
Normal file
@ -0,0 +1,84 @@
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import QtQuick
|
||||
import "../components"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
implicitWidth: hasBattery ? row.implicitWidth + 10 : 0
|
||||
implicitHeight: 24
|
||||
visible: hasBattery
|
||||
|
||||
property bool hasBattery: false
|
||||
property int percent: 0
|
||||
property string status: "Unknown" // "Charging", "Discharging", "Full", "Unknown"
|
||||
|
||||
// \uf0e7 = bolt (charging), \uf240-\uf244 = FA battery full…empty
|
||||
readonly property string battIcon: {
|
||||
if (status === "Charging" || status === "Full") return "\uf0e7"
|
||||
if (percent <= 10) return "\uf244"
|
||||
if (percent <= 25) return "\uf243"
|
||||
if (percent <= 50) return "\uf242"
|
||||
if (percent <= 75) return "\uf241"
|
||||
return "\uf240"
|
||||
}
|
||||
|
||||
readonly property bool isLow: percent <= 20 && status === "Discharging"
|
||||
|
||||
Timer {
|
||||
interval: 30000
|
||||
running: true
|
||||
repeat: true
|
||||
triggeredOnStart: true
|
||||
onTriggered: battProc.running = true
|
||||
}
|
||||
|
||||
Process {
|
||||
id: battProc
|
||||
// Find first BAT* supply; print "NONE" if none exists
|
||||
command: ["sh", "-c",
|
||||
"BAT=$(ls /sys/class/power_supply/ 2>/dev/null | grep -m1 '^BAT'); " +
|
||||
"[ -z \"$BAT\" ] && echo NONE && exit 0; " +
|
||||
"echo \"$(cat /sys/class/power_supply/$BAT/capacity):$(cat /sys/class/power_supply/$BAT/status)\""
|
||||
]
|
||||
stdout: SplitParser {
|
||||
splitMarker: "\n"
|
||||
onRead: data => {
|
||||
const line = data.trim()
|
||||
if (line === "" || line === "NONE") {
|
||||
root.hasBattery = false
|
||||
return
|
||||
}
|
||||
const sep = line.indexOf(":")
|
||||
if (sep < 0) return
|
||||
root.percent = parseInt(line.substring(0, sep)) || 0
|
||||
root.status = line.substring(sep + 1)
|
||||
root.hasBattery = true
|
||||
}
|
||||
}
|
||||
onExited: running = false
|
||||
}
|
||||
|
||||
Row {
|
||||
id: row
|
||||
anchors.centerIn: parent
|
||||
spacing: 5
|
||||
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: root.battIcon
|
||||
font.family: "JetBrainsMono Nerd Font Mono"
|
||||
font.pixelSize: 14
|
||||
color: root.status === "Charging" ? Theme.accent
|
||||
: root.isLow ? "#cc3333"
|
||||
: Theme.text
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: root.percent + "%"
|
||||
font.pixelSize: 11
|
||||
color: root.isLow ? "#cc3333" : Theme.textDim
|
||||
}
|
||||
}
|
||||
}
|
||||
139
roles/quickshell/files/bar/CalendarContent.qml
Normal file
139
roles/quickshell/files/bar/CalendarContent.qml
Normal file
@ -0,0 +1,139 @@
|
||||
import QtQuick
|
||||
import "../components"
|
||||
|
||||
// Calendar grid – a plain Item so it can live inside an expanding section border.
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property var now: new Date()
|
||||
|
||||
property int viewYear: now.getFullYear()
|
||||
property int viewMonth: now.getMonth() // 0-based
|
||||
|
||||
readonly property var monthNames: [
|
||||
"January","February","March","April","May","June",
|
||||
"July","August","September","October","November","December"
|
||||
]
|
||||
readonly property int daysInMonth: new Date(viewYear, viewMonth + 1, 0).getDate()
|
||||
// First weekday of month: Mon=0 … Sun=6
|
||||
readonly property int startOffset: (new Date(viewYear, viewMonth, 1).getDay() + 6) % 7
|
||||
readonly property int totalCells: Math.ceil((startOffset + daysInMonth) / 7) * 7
|
||||
|
||||
readonly property int todayYear: now.getFullYear()
|
||||
readonly property int todayMonth: now.getMonth()
|
||||
readonly property int todayDay: now.getDate()
|
||||
|
||||
// All widths derived from this constant — never from parent.width —
|
||||
// to avoid Column polish loops when the popup is wider than the grid.
|
||||
readonly property int cellW: 28
|
||||
readonly property int cellGap: 2
|
||||
readonly property int gridW: 7 * cellW + 6 * cellGap // 208
|
||||
|
||||
implicitWidth: gridW + 24
|
||||
implicitHeight: calLayout.implicitHeight + 16
|
||||
|
||||
Column {
|
||||
id: calLayout
|
||||
// Centre the fixed-width grid inside the (potentially wider) popup.
|
||||
// No left+right anchors → implicitWidth stays at gridW, no resize loop.
|
||||
anchors { top: parent.top; topMargin: 8; horizontalCenter: parent.horizontalCenter }
|
||||
spacing: 6
|
||||
|
||||
// Month navigation — all child widths are explicit constants
|
||||
Row {
|
||||
spacing: 0
|
||||
|
||||
Text {
|
||||
text: "‹"
|
||||
color: Theme.text
|
||||
font.pixelSize: 16
|
||||
width: 20
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (root.viewMonth === 0) { root.viewMonth = 11; root.viewYear-- }
|
||||
else root.viewMonth--
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.monthNames[root.viewMonth] + " " + root.viewYear
|
||||
color: Theme.text
|
||||
font.pixelSize: 13
|
||||
font.bold: true
|
||||
width: root.gridW - 40 // fixed constant, not parent.width
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "›"
|
||||
color: Theme.text
|
||||
font.pixelSize: 16
|
||||
width: 20
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (root.viewMonth === 11) { root.viewMonth = 0; root.viewYear++ }
|
||||
else root.viewMonth++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Weekday headers
|
||||
Row {
|
||||
spacing: root.cellGap
|
||||
Repeater {
|
||||
model: ["Mo","Tu","We","Th","Fr","Sa","Su"]
|
||||
Text {
|
||||
text: modelData
|
||||
color: Theme.textDim
|
||||
font.pixelSize: 10
|
||||
width: root.cellW
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Day grid
|
||||
Grid {
|
||||
columns: 7
|
||||
spacing: root.cellGap
|
||||
Repeater {
|
||||
model: root.totalCells
|
||||
delegate: Item {
|
||||
width: root.cellW
|
||||
height: 22
|
||||
readonly property int dayNum: index - root.startOffset + 1
|
||||
readonly property bool valid:
|
||||
index >= root.startOffset &&
|
||||
index < root.startOffset + root.daysInMonth
|
||||
readonly property bool isToday:
|
||||
valid &&
|
||||
root.viewYear === root.todayYear &&
|
||||
root.viewMonth === root.todayMonth &&
|
||||
dayNum === root.todayDay
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: 3
|
||||
color: isToday ? Theme.accent : "transparent"
|
||||
visible: isToday
|
||||
}
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: parent.valid ? String(parent.dayNum) : ""
|
||||
color: parent.isToday ? Theme.text : Theme.textDim
|
||||
font.pixelSize: 11
|
||||
font.bold: parent.isToday
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
47
roles/quickshell/files/bar/Clock.qml
Normal file
47
roles/quickshell/files/bar/Clock.qml
Normal file
@ -0,0 +1,47 @@
|
||||
import QtQuick
|
||||
import "../components"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
signal clicked
|
||||
|
||||
implicitWidth: timeLabel.implicitWidth + 16
|
||||
implicitHeight: 24
|
||||
|
||||
property var now: new Date()
|
||||
|
||||
Timer {
|
||||
interval: 1000
|
||||
running: true
|
||||
repeat: true
|
||||
onTriggered: root.now = new Date()
|
||||
}
|
||||
|
||||
readonly property string display: {
|
||||
const d = now
|
||||
const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
|
||||
const dd = String(d.getDate()).padStart(2, "0")
|
||||
const mm = String(d.getMonth() + 1).padStart(2, "0")
|
||||
const yyyy = d.getFullYear()
|
||||
const h = String(d.getHours()).padStart(2, "0")
|
||||
const m = String(d.getMinutes()).padStart(2, "0")
|
||||
const s = String(d.getSeconds()).padStart(2, "0")
|
||||
return days[d.getDay()] + " " + dd + "." + mm + "." + yyyy + " " + h + ":" + m + ":" + s
|
||||
}
|
||||
|
||||
Text {
|
||||
id: timeLabel
|
||||
anchors.centerIn: parent
|
||||
text: root.display
|
||||
color: Theme.text
|
||||
font.pixelSize: 12
|
||||
font.family: "JetBrainsMono Nerd Font Mono"
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: root.clicked()
|
||||
}
|
||||
}
|
||||
38
roles/quickshell/files/bar/MusicPlayer.qml
Normal file
38
roles/quickshell/files/bar/MusicPlayer.qml
Normal file
@ -0,0 +1,38 @@
|
||||
import Quickshell.Services.Mpris
|
||||
import QtQuick
|
||||
import "../components"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
signal clicked
|
||||
|
||||
// Pick the first playing player, fall back to first available
|
||||
readonly property var player: {
|
||||
const vals = Mpris.players.values
|
||||
for (const p of vals) {
|
||||
if (p.isPlaying) return p
|
||||
}
|
||||
return vals.length > 0 ? vals[0] : null
|
||||
}
|
||||
|
||||
visible: player !== null
|
||||
implicitWidth: player ? Math.min(240, titleLabel.implicitWidth + 20) : 0
|
||||
implicitHeight: 24
|
||||
|
||||
Text {
|
||||
id: titleLabel
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - 20
|
||||
text: root.player?.trackTitle ?? ""
|
||||
color: Theme.text
|
||||
font.pixelSize: 12
|
||||
elide: Text.ElideRight
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: root.clicked()
|
||||
}
|
||||
}
|
||||
151
roles/quickshell/files/bar/MusicPlayerControls.qml
Normal file
151
roles/quickshell/files/bar/MusicPlayerControls.qml
Normal file
@ -0,0 +1,151 @@
|
||||
import Quickshell.Services.Mpris
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import "../components"
|
||||
|
||||
// Plain Item – lives inside the expanding center section border in Bar.qml.
|
||||
Item {
|
||||
id: root
|
||||
required property var player
|
||||
|
||||
implicitWidth: 280
|
||||
implicitHeight: layout.implicitHeight + 28
|
||||
|
||||
// Keep the progress binding live while playing, pause while seeking.
|
||||
FrameAnimation {
|
||||
running: (root.player?.isPlaying ?? false) && !progressArea.pressed
|
||||
onTriggered: root.player.positionChanged()
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: layout
|
||||
anchors { fill: parent; margins: 14 }
|
||||
spacing: 8
|
||||
|
||||
// Track info
|
||||
Text {
|
||||
text: root.player?.trackTitle ?? ""
|
||||
color: Theme.text
|
||||
font.pixelSize: 13
|
||||
font.bold: true
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.player?.trackArtist ?? ""
|
||||
color: Theme.textDim
|
||||
font.pixelSize: 11
|
||||
Layout.fillWidth: true
|
||||
elide: Text.ElideRight
|
||||
visible: (root.player?.trackArtist ?? "") !== ""
|
||||
Layout.topMargin: -4
|
||||
}
|
||||
|
||||
// Progress bar
|
||||
Item {
|
||||
visible: root.player?.positionSupported && root.player?.lengthSupported
|
||||
&& (root.player?.length ?? 0) > 0
|
||||
Layout.fillWidth: true
|
||||
height: 12
|
||||
|
||||
Rectangle {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width
|
||||
height: 3
|
||||
radius: 2
|
||||
color: Theme.progressTrack
|
||||
|
||||
Rectangle {
|
||||
width: root.player?.length > 0
|
||||
? parent.width * (root.player.position / root.player.length)
|
||||
: 0
|
||||
height: parent.height
|
||||
radius: parent.radius
|
||||
color: Theme.accent
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: progressArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
function seek(mx) {
|
||||
if (!(root.player?.canSeek)) return
|
||||
const ratio = Math.max(0, Math.min(1, mx / width))
|
||||
root.player.position = ratio * (root.player?.length ?? 0)
|
||||
}
|
||||
|
||||
onClicked: seek(mouseX)
|
||||
onPositionChanged: if (pressed) seek(mouseX)
|
||||
}
|
||||
}
|
||||
|
||||
// Media controls
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
ControlButton {
|
||||
icon: "⏮"
|
||||
iconSize: 18
|
||||
enabled: root.player?.canGoPrevious ?? false
|
||||
onActivated: root.player.previous()
|
||||
}
|
||||
|
||||
Item { width: 8 }
|
||||
|
||||
ControlButton {
|
||||
icon: root.player?.isPlaying ? "⏸" : "▶"
|
||||
iconSize: 22
|
||||
enabled: true
|
||||
onActivated: root.player?.togglePlaying()
|
||||
}
|
||||
|
||||
Item { width: 8 }
|
||||
|
||||
ControlButton {
|
||||
icon: "⏭"
|
||||
iconSize: 18
|
||||
enabled: root.player?.canGoNext ?? false
|
||||
onActivated: root.player.next()
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
}
|
||||
}
|
||||
|
||||
component ControlButton: Rectangle {
|
||||
id: btn
|
||||
required property string icon
|
||||
required property int iconSize
|
||||
required property bool enabled
|
||||
signal activated
|
||||
|
||||
implicitWidth: label.implicitWidth + 16
|
||||
implicitHeight: label.implicitHeight + 10
|
||||
radius: Theme.radius
|
||||
color: btnArea.containsMouse ? Qt.rgba(1, 1, 1, 0.08) : 'transparent'
|
||||
Behavior on color { ColorAnimation { duration: 80 } }
|
||||
|
||||
Text {
|
||||
id: label
|
||||
anchors.centerIn: parent
|
||||
text: btn.icon
|
||||
font.pixelSize: btn.iconSize
|
||||
color: btn.enabled ? Theme.text : Theme.textDisabled
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: btnArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: if (btn.enabled) btn.activated()
|
||||
}
|
||||
}
|
||||
}
|
||||
86
roles/quickshell/files/bar/NetworkStatus.qml
Normal file
86
roles/quickshell/files/bar/NetworkStatus.qml
Normal file
@ -0,0 +1,86 @@
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import QtQuick
|
||||
import "../components"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
implicitWidth: row.implicitWidth + 10
|
||||
implicitHeight: 24
|
||||
|
||||
property string connType: "none" // "wifi", "ethernet", "none"
|
||||
property string connName: ""
|
||||
|
||||
// accumulate results here, flush to displayed props on exit
|
||||
property string _pendingType: "none"
|
||||
property string _pendingName: ""
|
||||
|
||||
// \uf1eb = FA wifi, \uf0e8 = FA sitemap (wired proxy), \uf127 = FA chain-broken
|
||||
readonly property string netIcon:
|
||||
connType === "wifi" ? "\uf1eb" :
|
||||
connType === "ethernet" ? "\uf0e8" : "\uf127"
|
||||
|
||||
function parseLine(line) {
|
||||
const idx1 = line.indexOf(":")
|
||||
if (idx1 < 0) return
|
||||
const idx2 = line.indexOf(":", idx1 + 1)
|
||||
if (idx2 < 0) return
|
||||
const type = line.substring(0, idx1)
|
||||
const state = line.substring(idx1 + 1, idx2)
|
||||
const conn = line.substring(idx2 + 1).trim()
|
||||
if (state === "connected" && (type === "wifi" || type === "ethernet")) {
|
||||
if (root._pendingType === "none" || type === "wifi") {
|
||||
root._pendingType = type
|
||||
root._pendingName = conn
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 5000
|
||||
running: true
|
||||
repeat: true
|
||||
triggeredOnStart: true
|
||||
onTriggered: {
|
||||
root._pendingType = "none"
|
||||
root._pendingName = ""
|
||||
netProc.running = true
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: netProc
|
||||
command: ["nmcli", "-t", "-f", "TYPE,STATE,CONNECTION", "dev"]
|
||||
stdout: SplitParser {
|
||||
splitMarker: "\n"
|
||||
onRead: data => root.parseLine(data)
|
||||
}
|
||||
onExited: {
|
||||
root.connType = root._pendingType
|
||||
root.connName = root._pendingName
|
||||
running = false
|
||||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: row
|
||||
anchors.centerIn: parent
|
||||
spacing: 5
|
||||
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: root.netIcon
|
||||
font.family: "JetBrainsMono Nerd Font Mono"
|
||||
font.pixelSize: 14
|
||||
color: root.connType !== "none" ? Theme.text : Theme.textDim
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: root.connName.length > 16 ? root.connName.substring(0, 15) + "…" : root.connName
|
||||
font.pixelSize: 11
|
||||
color: Theme.textDim
|
||||
visible: root.connName !== ""
|
||||
}
|
||||
}
|
||||
}
|
||||
162
roles/quickshell/files/bar/PopoutWindow.qml
Normal file
162
roles/quickshell/files/bar/PopoutWindow.qml
Normal file
@ -0,0 +1,162 @@
|
||||
import Quickshell
|
||||
import QtQuick
|
||||
import QtQuick.Shapes
|
||||
import "../components"
|
||||
|
||||
PopupWindow {
|
||||
id: self
|
||||
|
||||
property string popupName: ""
|
||||
property string activePopup: ""
|
||||
|
||||
visible: false
|
||||
color: "transparent"
|
||||
|
||||
default property alias content: contentSlot.data
|
||||
|
||||
readonly property int bw: Theme.borderWidth
|
||||
readonly property real r: Theme.radius
|
||||
|
||||
readonly property bool _open: activePopup === popupName
|
||||
on_OpenChanged: {
|
||||
if (_open) {
|
||||
hideTimer.stop();
|
||||
self.visible = true;
|
||||
fade.opacity = 1.0;
|
||||
} else {
|
||||
fade.opacity = 0.0;
|
||||
hideTimer.start();
|
||||
}
|
||||
}
|
||||
Timer {
|
||||
id: hideTimer
|
||||
interval: 200
|
||||
onTriggered: self.visible = false
|
||||
}
|
||||
|
||||
Item {
|
||||
id: fade
|
||||
anchors.fill: parent
|
||||
opacity: 0.0
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: 180
|
||||
easing.type: Easing.InOutCubic
|
||||
}
|
||||
}
|
||||
|
||||
// Background + border in one Shape so fill matches stroke exactly
|
||||
Shape {
|
||||
id: pbs
|
||||
anchors.fill: parent
|
||||
|
||||
ShapePath {
|
||||
strokeWidth: self.bw
|
||||
strokeColor: Theme.border
|
||||
fillColor: Theme.bg
|
||||
|
||||
// Start flush at top-left, hidden under bar
|
||||
startX: 0
|
||||
startY: self.r
|
||||
|
||||
// Left side down to bottom-left corner
|
||||
PathLine {
|
||||
x: 0
|
||||
y: pbs.height - self.r
|
||||
}
|
||||
|
||||
// Bottom-left corner — Counterclockwise for outward curve
|
||||
PathArc {
|
||||
x: self.r
|
||||
y: pbs.height
|
||||
radiusX: self.r
|
||||
radiusY: self.r
|
||||
direction: PathArc.Counterclockwise
|
||||
}
|
||||
|
||||
// Bottom edge
|
||||
PathLine {
|
||||
x: pbs.width - self.r
|
||||
y: pbs.height
|
||||
}
|
||||
|
||||
// Bottom-right corner — Counterclockwise for outward curve
|
||||
PathArc {
|
||||
x: pbs.width
|
||||
y: pbs.height - self.r
|
||||
radiusX: self.r
|
||||
radiusY: self.r
|
||||
direction: PathArc.Counterclockwise
|
||||
}
|
||||
|
||||
// Right side up, flush to top, hidden under bar
|
||||
PathLine {
|
||||
x: pbs.width
|
||||
y: self.r
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// // Top-left concave corner
|
||||
// Shape {
|
||||
// x: 0
|
||||
// y: 0
|
||||
// width: self.r
|
||||
// height: self.r
|
||||
// ShapePath {
|
||||
// fillColor: Theme.bg
|
||||
// strokeColor: "transparent"
|
||||
// startX: 0
|
||||
// startY: 0
|
||||
// PathLine {
|
||||
// x: self.r
|
||||
// y: 0
|
||||
// }
|
||||
// PathArc {
|
||||
// x: 0
|
||||
// y: self.r
|
||||
// radiusX: self.r
|
||||
// radiusY: self.r
|
||||
// direction: PathArc.Clockwise
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // Top-right concave corner
|
||||
// Shape {
|
||||
// x: parent.width - self.r
|
||||
// y: 0
|
||||
// width: self.r
|
||||
// height: self.r
|
||||
// ShapePath {
|
||||
// fillColor: Theme.bg
|
||||
// strokeColor: "transparent"
|
||||
// startX: self.r
|
||||
// startY: 0
|
||||
// PathLine {
|
||||
// x: 0
|
||||
// y: 0
|
||||
// }
|
||||
// PathArc {
|
||||
// x: self.r
|
||||
// y: self.r
|
||||
// radiusX: self.r
|
||||
// radiusY: self.r
|
||||
// direction: PathArc.Counterclockwise
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
Item {
|
||||
id: contentSlot
|
||||
anchors {
|
||||
top: parent.top
|
||||
topMargin: self.r + self.bw
|
||||
left: parent.left
|
||||
leftMargin: self.bw
|
||||
right: parent.right
|
||||
rightMargin: self.bw
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
65
roles/quickshell/files/bar/SysTray.qml
Normal file
65
roles/quickshell/files/bar/SysTray.qml
Normal file
@ -0,0 +1,65 @@
|
||||
import Quickshell
|
||||
import Quickshell.Services.SystemTray
|
||||
import Quickshell.Widgets
|
||||
import QtQuick
|
||||
import "../components"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
required property var barWindow
|
||||
implicitWidth: trayRow.width
|
||||
implicitHeight: 24
|
||||
|
||||
Row {
|
||||
id: trayRow
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 3
|
||||
|
||||
Repeater {
|
||||
model: SystemTray.items
|
||||
|
||||
delegate: Item {
|
||||
id: trayDelegate
|
||||
required property var modelData
|
||||
|
||||
width: 24
|
||||
height: 24
|
||||
|
||||
// Hover highlight
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Theme.radius
|
||||
color: hoverArea.containsMouse ? Qt.rgba(1,1,1,0.08) : "transparent"
|
||||
Behavior on color { ColorAnimation { duration: 80 } }
|
||||
}
|
||||
|
||||
TrayIcon {
|
||||
anchors.centerIn: parent
|
||||
icon: trayDelegate.modelData.icon
|
||||
size: 16
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: hoverArea
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: mouse => {
|
||||
const item = trayDelegate.modelData
|
||||
const wantsMenu = mouse.button === Qt.RightButton || item.onlyMenu
|
||||
if (wantsMenu && item.hasMenu) {
|
||||
// display() needs the quickshell PanelWindow (not QQuickWindow).
|
||||
// mapToItem(null) gives scene/window-local coordinates.
|
||||
const pos = trayDelegate.mapToItem(null, 0, trayDelegate.height)
|
||||
item.display(root.barWindow, Math.round(pos.x), Math.round(pos.y))
|
||||
} else if (!item.onlyMenu) {
|
||||
item.activate()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
74
roles/quickshell/files/bar/VolumeControl.qml
Normal file
74
roles/quickshell/files/bar/VolumeControl.qml
Normal file
@ -0,0 +1,74 @@
|
||||
import Quickshell.Services.Pipewire
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import "../components"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
implicitWidth: chip.implicitWidth + 10
|
||||
implicitHeight: 24
|
||||
|
||||
signal clickedLeft
|
||||
|
||||
PwObjectTracker {
|
||||
objects: Pipewire.nodes.values
|
||||
}
|
||||
|
||||
function safeVolume(node) {
|
||||
if (!node || !node.ready || !node.audio) return 0
|
||||
const v = node.audio.volume
|
||||
return (v !== undefined && !isNaN(v)) ? v : 0
|
||||
}
|
||||
function setVolume(node, v) {
|
||||
if (!node || !node.ready || !node.audio) return
|
||||
node.audio.volume = Math.max(0, Math.min(1, v))
|
||||
}
|
||||
function volIcon(vol, muted) {
|
||||
if (muted) return "\uf6a9"
|
||||
if (vol > 0.6) return "\uf028"
|
||||
if (vol > 0.2) return "\uf027"
|
||||
return "\uf026"
|
||||
}
|
||||
|
||||
readonly property var defaultSink: Pipewire.defaultAudioSink
|
||||
readonly property real defaultVolume: safeVolume(defaultSink)
|
||||
readonly property bool defaultMuted: defaultSink?.audio?.muted ?? false
|
||||
|
||||
Row {
|
||||
id: chip
|
||||
anchors.centerIn: parent
|
||||
spacing: 5
|
||||
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: root.volIcon(root.defaultVolume, root.defaultMuted)
|
||||
font.family: "JetBrainsMono Nerd Font Mono"
|
||||
font.pixelSize: 14
|
||||
color: root.defaultMuted ? Theme.textDim : Theme.text
|
||||
}
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: root.defaultMuted ? "mute" : Math.round(root.defaultVolume * 100) + "%"
|
||||
font.pixelSize: 11
|
||||
color: Theme.textDim
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: mouse => {
|
||||
if (mouse.button === Qt.RightButton) {
|
||||
if (root.defaultSink?.ready && root.defaultSink?.audio)
|
||||
root.defaultSink.audio.muted = !root.defaultSink.audio.muted
|
||||
} else {
|
||||
root.clickedLeft()
|
||||
}
|
||||
}
|
||||
onWheel: wheel => {
|
||||
if (!root.defaultSink?.ready) return
|
||||
root.setVolume(root.defaultSink, root.defaultVolume + wheel.angleDelta.y / 120 * 0.05)
|
||||
}
|
||||
}
|
||||
}
|
||||
143
roles/quickshell/files/bar/VolumeMixerContent.qml
Normal file
143
roles/quickshell/files/bar/VolumeMixerContent.qml
Normal file
@ -0,0 +1,143 @@
|
||||
import Quickshell.Services.Pipewire
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import "../components"
|
||||
|
||||
// Volume mixer – a plain Item so it can live inside an expanding section border.
|
||||
Item {
|
||||
id: root
|
||||
|
||||
implicitWidth: 300
|
||||
implicitHeight: mixerCol.implicitHeight + 24
|
||||
|
||||
PwObjectTracker {
|
||||
objects: Pipewire.nodes.values
|
||||
}
|
||||
|
||||
function safeVolume(node) {
|
||||
if (!node || !node.ready || !node.audio) return 0
|
||||
const v = node.audio.volume
|
||||
return (v !== undefined && !isNaN(v)) ? v : 0
|
||||
}
|
||||
function setVolume(node, v) {
|
||||
if (!node || !node.ready || !node.audio) return
|
||||
node.audio.volume = Math.max(0, Math.min(1, v))
|
||||
}
|
||||
function volIcon(vol, muted) {
|
||||
if (muted) return "\uf6a9"
|
||||
if (vol > 0.6) return "\uf028"
|
||||
if (vol > 0.2) return "\uf027"
|
||||
return "\uf026"
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: mixerCol
|
||||
anchors { fill: parent; margins: 12 }
|
||||
spacing: 4
|
||||
|
||||
Text {
|
||||
text: "Output Devices"
|
||||
color: Theme.textDim
|
||||
font.pixelSize: 10
|
||||
Layout.fillWidth: true
|
||||
Layout.bottomMargin: 2
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: Pipewire.nodes
|
||||
delegate: NodeRow {
|
||||
required property var modelData
|
||||
Layout.fillWidth: true
|
||||
node: modelData
|
||||
visible: modelData.isSink && !modelData.isStream && modelData.ready
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Applications"
|
||||
color: Theme.textDim
|
||||
font.pixelSize: 10
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 6
|
||||
Layout.bottomMargin: 2
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: Pipewire.nodes
|
||||
delegate: NodeRow {
|
||||
required property var modelData
|
||||
Layout.fillWidth: true
|
||||
node: modelData
|
||||
visible: modelData.isStream && !modelData.isSink && modelData.ready
|
||||
&& !(modelData.description ?? "").toLowerCase().includes("monitor")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
component NodeRow: RowLayout {
|
||||
id: row
|
||||
required property var node
|
||||
spacing: 8
|
||||
implicitHeight: 28
|
||||
|
||||
Text {
|
||||
text: root.volIcon(root.safeVolume(row.node), row.node.audio?.muted ?? false)
|
||||
font.family: "JetBrainsMono Nerd Font Mono"
|
||||
font.pixelSize: 13
|
||||
color: (row.node.audio?.muted ?? false) ? Theme.textDim : Theme.text
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (row.node.ready && row.node.audio)
|
||||
row.node.audio.muted = !row.node.audio.muted
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: row.node.description || row.node.nickname || row.node.name || "?"
|
||||
font.pixelSize: 11
|
||||
color: Theme.textDim
|
||||
Layout.preferredWidth: 90
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: 16
|
||||
|
||||
Rectangle {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width
|
||||
height: 3
|
||||
radius: 2
|
||||
color: Theme.progressTrack
|
||||
|
||||
Rectangle {
|
||||
width: parent.width * Math.min(1, root.safeVolume(row.node))
|
||||
height: parent.height
|
||||
radius: parent.radius
|
||||
color: Theme.accent
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors { fill: parent; topMargin: -4; bottomMargin: -4 }
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
function seek(mx) { root.setVolume(row.node, mx / width) }
|
||||
onClicked: seek(mouseX)
|
||||
onPositionChanged: if (pressed) seek(mouseX)
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: Math.round(root.safeVolume(row.node) * 100) + "%"
|
||||
font.pixelSize: 10
|
||||
color: Theme.textDim
|
||||
Layout.preferredWidth: 30
|
||||
horizontalAlignment: Text.AlignRight
|
||||
}
|
||||
}
|
||||
}
|
||||
70
roles/quickshell/files/bar/WorkspaceButton.qml
Normal file
70
roles/quickshell/files/bar/WorkspaceButton.qml
Normal file
@ -0,0 +1,70 @@
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
// import Quickshell.Widgets
|
||||
import QtQuick
|
||||
import "../components"
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
required property var workspace
|
||||
required property string screenName
|
||||
|
||||
visible: workspace?.lastIpcObject?.monitor === screenName
|
||||
|
||||
implicitWidth: Math.max(32, iconsRow.implicitWidth + 14)
|
||||
implicitHeight: 18
|
||||
radius: 6
|
||||
color: workspace?.active ? Theme.accent : 'transparent'
|
||||
|
||||
Row {
|
||||
id: iconsRow
|
||||
anchors.centerIn: parent
|
||||
spacing: 3
|
||||
|
||||
Repeater {
|
||||
model: root.workspace?.toplevels
|
||||
|
||||
delegate: Item {
|
||||
id: iconItem
|
||||
required property var modelData
|
||||
|
||||
implicitWidth: 16
|
||||
implicitHeight: 16
|
||||
|
||||
property string appClass: ""
|
||||
|
||||
Component.onCompleted: {
|
||||
var cls = modelData?.lastIpcObject?.["class"] ?? ""
|
||||
if (cls !== "") {
|
||||
appClass = cls
|
||||
} else if (modelData) {
|
||||
modelData.lastIpcObjectChanged.connect(function() {
|
||||
var c = iconItem.modelData?.lastIpcObject?.["class"] ?? ""
|
||||
if (c !== "") iconItem.appClass = c
|
||||
})
|
||||
Qt.callLater(Hyprland.refreshToplevels)
|
||||
}
|
||||
}
|
||||
|
||||
TrayIcon {
|
||||
anchors.fill: parent
|
||||
icon: iconItem.appClass
|
||||
size: 16
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Workspace number shown when no windows are open
|
||||
Text {
|
||||
visible: (root.workspace?.toplevels?.values?.length ?? 0) === 0
|
||||
text: root.workspace?.id ?? ""
|
||||
color: root.workspace?.active ? Theme.text : Theme.textDim
|
||||
font.pixelSize: 11
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: Hyprland.dispatch("workspace " + root.workspace.id)
|
||||
}
|
||||
}
|
||||
22
roles/quickshell/files/bar/Workspaces.qml
Normal file
22
roles/quickshell/files/bar/Workspaces.qml
Normal file
@ -0,0 +1,22 @@
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import Quickshell.Hyprland
|
||||
import QtQuick
|
||||
|
||||
Row {
|
||||
id: root
|
||||
required property string screenName
|
||||
spacing: 4
|
||||
|
||||
Repeater {
|
||||
id: workspacesRepeater
|
||||
model: Hyprland.workspaces
|
||||
property string screenName: root.screenName
|
||||
|
||||
delegate: WorkspaceButton {
|
||||
required property var modelData
|
||||
workspace: modelData
|
||||
screenName: workspacesRepeater.screenName
|
||||
}
|
||||
}
|
||||
}
|
||||
13
roles/quickshell/files/bar/qmldir
Normal file
13
roles/quickshell/files/bar/qmldir
Normal file
@ -0,0 +1,13 @@
|
||||
Bar 1.0 Bar.qml
|
||||
Battery 1.0 Battery.qml
|
||||
PopoutWindow 1.0 PopoutWindow.qml
|
||||
CalendarContent 1.0 CalendarContent.qml
|
||||
Clock 1.0 Clock.qml
|
||||
MusicPlayer 1.0 MusicPlayer.qml
|
||||
MusicPlayerControls 1.0 MusicPlayerControls.qml
|
||||
NetworkStatus 1.0 NetworkStatus.qml
|
||||
SysTray 1.0 SysTray.qml
|
||||
VolumeControl 1.0 VolumeControl.qml
|
||||
VolumeMixerContent 1.0 VolumeMixerContent.qml
|
||||
Workspaces 1.0 Workspaces.qml
|
||||
WorkspaceButton 1.0 WorkspaceButton.qml
|
||||
11
roles/quickshell/files/components/Enclosure.qml
Normal file
11
roles/quickshell/files/components/Enclosure.qml
Normal file
@ -0,0 +1,11 @@
|
||||
import QtQuick
|
||||
import Quickshell.Widgets
|
||||
import "."
|
||||
|
||||
WrapperRectangle {
|
||||
radius: Theme.radius
|
||||
color: 'transparent'
|
||||
border.color: Theme.border
|
||||
border.width: Theme.borderWidth
|
||||
margin: Theme.enclosureMargin
|
||||
}
|
||||
19
roles/quickshell/files/components/Theme.qml
Normal file
19
roles/quickshell/files/components/Theme.qml
Normal file
@ -0,0 +1,19 @@
|
||||
pragma Singleton
|
||||
import QtQuick
|
||||
|
||||
QtObject {
|
||||
// Core palette
|
||||
readonly property color bg: '#D9000000' // bar background (semi-transparent)
|
||||
readonly property color bgPopup: '#000000' // popup background (fully opaque)
|
||||
readonly property color accent: '#9B1A1A'
|
||||
readonly property color border: '#FFFFFF'
|
||||
readonly property color text: '#FFFFFF'
|
||||
readonly property color textDim: '#888888'
|
||||
readonly property color textDisabled: '#444444'
|
||||
readonly property color progressTrack: '#333333'
|
||||
|
||||
// Shape / sizing
|
||||
readonly property int radius: 16
|
||||
readonly property int borderWidth: 2
|
||||
readonly property int enclosureMargin: 3
|
||||
}
|
||||
103
roles/quickshell/files/components/TrayIcon.qml
Normal file
103
roles/quickshell/files/components/TrayIcon.qml
Normal file
@ -0,0 +1,103 @@
|
||||
// import QtQuick
|
||||
// import Quickshell.Widgets
|
||||
//
|
||||
// Item {
|
||||
// id: root
|
||||
//
|
||||
// property string icon: ""
|
||||
// property int size: 16
|
||||
//
|
||||
// implicitWidth: size
|
||||
// implicitHeight: size
|
||||
//
|
||||
// readonly property url resolvedSource: {
|
||||
// if (!icon) return ""
|
||||
// if (icon.startsWith("/") || icon.startsWith("file://") || icon.startsWith("image://"))
|
||||
// return icon
|
||||
// if (icon.includes("?path=")) {
|
||||
// const [name, path] = icon.split("?path=")
|
||||
// const baseName = name.slice(name.lastIndexOf("/") + 1)
|
||||
// return `file://${path}/${baseName}`
|
||||
// }
|
||||
// return `image://icon/${icon}`
|
||||
// }
|
||||
//
|
||||
// readonly property bool isIconTheme: resolvedSource.toString().startsWith("image://icon/")
|
||||
// readonly property bool hasSource: resolvedSource.toString() !== ""
|
||||
//
|
||||
// IconImage {
|
||||
// anchors.fill: parent
|
||||
// source: root.resolvedSource
|
||||
// visible: root.hasSource && root.isIconTheme
|
||||
// asynchronous: true
|
||||
// }
|
||||
//
|
||||
// Image {
|
||||
// anchors.fill: parent
|
||||
// source: root.hasSource && !root.isIconTheme ? root.resolvedSource : ""
|
||||
// visible: root.hasSource && !root.isIconTheme
|
||||
// asynchronous: true
|
||||
// fillMode: Image.PreserveAspectFit
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
// import Quickshell
|
||||
import Quickshell.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property string icon: ""
|
||||
property int size: 16
|
||||
|
||||
implicitWidth: size
|
||||
implicitHeight: size
|
||||
|
||||
readonly property url resolvedSource: {
|
||||
if (!icon) return ""
|
||||
if (icon.startsWith("/") || icon.startsWith("file://") || icon.startsWith("image://"))
|
||||
return icon
|
||||
if (icon.includes("?path=")) {
|
||||
const [name, path] = icon.split("?path=")
|
||||
const baseName = name.slice(name.lastIndexOf("/") + 1)
|
||||
return `file://${path}/${baseName}`
|
||||
}
|
||||
// Use Quickshell's icon image provider instead of iconPath()
|
||||
return `image://icon/${icon}`
|
||||
}
|
||||
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
// asynchronous: true
|
||||
sourceComponent: root.resolvedSource && root.resolvedSource.toString() !== ""
|
||||
? root.resolvedSource.toString().startsWith("image://icon/")
|
||||
? iconComponent
|
||||
: imageComponent
|
||||
: null
|
||||
}
|
||||
|
||||
Component {
|
||||
id: iconComponent
|
||||
IconImage {
|
||||
source: root.resolvedSource
|
||||
asynchronous: true
|
||||
// mipmap: true
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: imageComponent
|
||||
Image {
|
||||
source: root.resolvedSource
|
||||
asynchronous: true
|
||||
// mipmap: true
|
||||
fillMode: Image.PreserveAspectFit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
3
roles/quickshell/files/components/qmldir
Normal file
3
roles/quickshell/files/components/qmldir
Normal file
@ -0,0 +1,3 @@
|
||||
singleton Theme 1.0 Theme.qml
|
||||
Enclosure 1.0 Enclosure.qml
|
||||
TrayIcon 1.0 TrayIcon.qml
|
||||
13
roles/quickshell/files/shell.qml
Normal file
13
roles/quickshell/files/shell.qml
Normal file
@ -0,0 +1,13 @@
|
||||
//@ pragma UseQApplication
|
||||
import Quickshell
|
||||
import "bar"
|
||||
|
||||
ShellRoot {
|
||||
Variants {
|
||||
model: Quickshell.screens
|
||||
|
||||
Bar { }
|
||||
// // modelData injected by Variants; Bar.qml binds screen: modelData internally
|
||||
// }
|
||||
}
|
||||
}
|
||||
51
roles/quickshell/tasks/main.yml
Normal file
51
roles/quickshell/tasks/main.yml
Normal file
@ -0,0 +1,51 @@
|
||||
---
|
||||
- name: Check if quickshell path is a symlink
|
||||
stat:
|
||||
path: "{{ config_dir }}/quickshell"
|
||||
register: quickshell_stat
|
||||
|
||||
- name: Remove existing quickshell symlink if present
|
||||
file:
|
||||
path: "{{ config_dir }}/quickshell"
|
||||
state: absent
|
||||
when: quickshell_stat.stat.exists and quickshell_stat.stat.islnk
|
||||
|
||||
- name: Create quickshell config directories
|
||||
file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
mode: '0755'
|
||||
loop:
|
||||
- "{{ config_dir }}/quickshell"
|
||||
- "{{ config_dir }}/quickshell/bar"
|
||||
- "{{ config_dir }}/quickshell/components"
|
||||
|
||||
- name: Symlink quickshell config files
|
||||
file:
|
||||
src: "{{ role_path }}/files/{{ item.src }}"
|
||||
dest: "{{ config_dir }}/quickshell/{{ item.dest }}"
|
||||
state: link
|
||||
force: true
|
||||
loop:
|
||||
# Root
|
||||
- { src: shell.qml, dest: shell.qml }
|
||||
# Shared components module
|
||||
- { src: components/Theme.qml, dest: components/Theme.qml }
|
||||
- { src: components/Enclosure.qml, dest: components/Enclosure.qml }
|
||||
- { src: components/TrayIcon.qml, dest: components/TrayIcon.qml }
|
||||
- { src: components/qmldir, dest: components/qmldir }
|
||||
# Bar module (imported as "bar" in shell.qml)
|
||||
- { src: bar/Bar.qml, dest: bar/Bar.qml }
|
||||
- { src: bar/Battery.qml, dest: bar/Battery.qml }
|
||||
- { src: bar/CalendarContent.qml, dest: bar/CalendarContent.qml }
|
||||
- { src: bar/Clock.qml, dest: bar/Clock.qml }
|
||||
- { src: bar/MusicPlayer.qml, dest: bar/MusicPlayer.qml }
|
||||
- { src: bar/MusicPlayerControls.qml, dest: bar/MusicPlayerControls.qml }
|
||||
- { src: bar/NetworkStatus.qml, dest: bar/NetworkStatus.qml }
|
||||
- { src: bar/PopoutWindow.qml, dest: bar/PopoutWindow.qml }
|
||||
- { src: bar/SysTray.qml, dest: bar/SysTray.qml }
|
||||
- { src: bar/VolumeControl.qml, dest: bar/VolumeControl.qml }
|
||||
- { src: bar/VolumeMixerContent.qml, dest: bar/VolumeMixerContent.qml }
|
||||
- { src: bar/Workspaces.qml, dest: bar/Workspaces.qml }
|
||||
- { src: bar/WorkspaceButton.qml, dest: bar/WorkspaceButton.qml }
|
||||
- { src: bar/qmldir, dest: bar/qmldir }
|
||||
@ -4,12 +4,14 @@
|
||||
update_cache: yes
|
||||
when: ansible_facts.os_family == "Archlinux"
|
||||
become: yes
|
||||
become_user: root
|
||||
|
||||
- name: Install system packages
|
||||
package:
|
||||
name: "{{ system_packages }}"
|
||||
state: present
|
||||
become: yes
|
||||
become_user: root
|
||||
|
||||
- name: Ensure .config directory exists
|
||||
file:
|
||||
|
||||
8
roles/zsh/files/.zshenv
Normal file
8
roles/zsh/files/.zshenv
Normal file
@ -0,0 +1,8 @@
|
||||
export PATH="/home/johannes/.local/bin:$PATH"
|
||||
# pnpm
|
||||
export PNPM_HOME="$HOME/.local/share/pnpm"
|
||||
case ":$PATH:" in
|
||||
*":$PNPM_HOME:"*) ;;
|
||||
*) export PATH="$PNPM_HOME:$PATH" ;;
|
||||
esac
|
||||
# pnpm end
|
||||
@ -52,3 +52,7 @@ alias drun='docker run -it --network=host --device=/dev/kfd --device=/dev/dri --
|
||||
export SSH_AUTH_SOCK="$XDG_RUNTIME_DIR/gcr/ssh"
|
||||
|
||||
bindkey '^H' backward-kill-word
|
||||
bindkey '^R' fzf-history-widget
|
||||
|
||||
eval "$(zoxide init zsh)"
|
||||
|
||||
|
||||
@ -18,3 +18,7 @@ rm() {
|
||||
print "⚠️ To use real rm, use /bin/rm"
|
||||
trash-put "$@"
|
||||
}
|
||||
|
||||
alias lg='lazygit'
|
||||
|
||||
alias tt='taskwarrior-tui'
|
||||
|
||||
@ -1 +1 @@
|
||||
source /usr/share/autojump/autojump.zsh
|
||||
source <(fzf --zsh)
|
||||
|
||||
@ -3,22 +3,25 @@
|
||||
package:
|
||||
name: zsh
|
||||
state: present
|
||||
become: yes
|
||||
become_user: root
|
||||
|
||||
- name: Install oh-my-zsh
|
||||
shell: |
|
||||
sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" --unattended
|
||||
args:
|
||||
creates: "{{ ansible_facts.env.HOME }}/.oh-my-zsh"
|
||||
creates: "{{ home_dir }}/.oh-my-zsh"
|
||||
|
||||
- name: Check for custom zshrc
|
||||
stat:
|
||||
path: "{{ role_path }}/files/.zshrc"
|
||||
register: custom_zshrc
|
||||
|
||||
- name: Symlink custom zshrc
|
||||
- name: Symlink .zshenv
|
||||
file:
|
||||
src: "{{ role_path }}/files/.zshrc"
|
||||
dest: "{{ ansible_facts.env.HOME }}/.zshrc"
|
||||
src: "{{ role_path }}/files/.zshenv"
|
||||
dest: "{{ home_dir }}/.zshenv"
|
||||
state: link
|
||||
force: yes
|
||||
|
||||
- name: Symlink .zshrc
|
||||
file:
|
||||
src: "{{ role_path }}/files/.zshrc"
|
||||
dest: "{{ home_dir }}/.zshrc"
|
||||
state: link
|
||||
force: yes
|
||||
when: custom_zshrc.stat.exists
|
||||
|
||||
Reference in New Issue
Block a user