mirror of
https://github.com/moons-14/dotfiles.git
synced 2026-10-06 18:04:08 +09:00
noctalia
This commit is contained in:
+22
-1
@@ -1,6 +1,27 @@
|
|||||||
{
|
{
|
||||||
|
|
||||||
|
services.kanshi = {
|
||||||
|
enable = true;
|
||||||
|
systemdTarget = "labwc-session.target";
|
||||||
|
settings = [
|
||||||
|
{
|
||||||
|
profile = {
|
||||||
|
name = "x1g13";
|
||||||
|
outputs = [
|
||||||
|
{
|
||||||
|
criteria = "eDP-1";
|
||||||
|
status = "enable";
|
||||||
|
position = "0,0";
|
||||||
|
scale = 1.5;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
programs.niri.settings.outputs."eDP-1" = {
|
programs.niri.settings.outputs."eDP-1" = {
|
||||||
scale = 1.2;
|
scale = 1.5;
|
||||||
position = {
|
position = {
|
||||||
x = 0;
|
x = 0;
|
||||||
y = 0;
|
y = 0;
|
||||||
|
|||||||
@@ -151,6 +151,10 @@ in
|
|||||||
# Pull in graphical-session.target so Home Manager services such as
|
# Pull in graphical-session.target so Home Manager services such as
|
||||||
# portals, Vicinae, and the idle manager follow the labwc session.
|
# portals, Vicinae, and the idle manager follow the labwc session.
|
||||||
systemd.extraCommands = [
|
systemd.extraCommands = [
|
||||||
|
# Clear the previous session's target first. Otherwise services such as
|
||||||
|
# kanshi that lose their Wayland connection on logout remain in a
|
||||||
|
# failed/start-limit-hit state and are not started for the next login.
|
||||||
|
"${lib.getExe' pkgs.systemd "systemctl"} --user stop labwc-session.target"
|
||||||
"${lib.getExe' pkgs.systemd "systemctl"} --user --no-block start labwc-session.target"
|
"${lib.getExe' pkgs.systemd "systemctl"} --user --no-block start labwc-session.target"
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
{ lib, pkgs }:
|
||||||
|
let
|
||||||
|
version = "0.46.0";
|
||||||
|
architecture =
|
||||||
|
if pkgs.stdenv.hostPlatform.isx86_64 then
|
||||||
|
"x86_64"
|
||||||
|
else if pkgs.stdenv.hostPlatform.isAarch64 then
|
||||||
|
"aarch64"
|
||||||
|
else
|
||||||
|
throw "CodexBar CLI is only packaged for x86_64-linux and aarch64-linux";
|
||||||
|
hash =
|
||||||
|
{
|
||||||
|
x86_64 = "sha256-yMrOpu1WIv2sGVgvY9qf2XCoX2AXMSqR2b8bQDRU6os=";
|
||||||
|
aarch64 = "sha256-pCp3NOlxFN6zeH67W04WGSPbUae+ktzR5vEunWqu00g=";
|
||||||
|
}
|
||||||
|
.${architecture};
|
||||||
|
in
|
||||||
|
pkgs.stdenvNoCC.mkDerivation {
|
||||||
|
pname = "codexbar";
|
||||||
|
inherit version;
|
||||||
|
|
||||||
|
src = pkgs.fetchurl {
|
||||||
|
url = "https://github.com/steipete/CodexBar/releases/download/v${version}/CodexBarCLI-v${version}-linux-musl-${architecture}.tar.gz";
|
||||||
|
inherit hash;
|
||||||
|
};
|
||||||
|
|
||||||
|
dontUnpack = true;
|
||||||
|
|
||||||
|
installPhase = ''
|
||||||
|
tar -xzf "$src" CodexBarCLI
|
||||||
|
install -Dm755 CodexBarCLI "$out/bin/codexbar"
|
||||||
|
'';
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
description = "CLI for CodexBar usage data";
|
||||||
|
homepage = "https://github.com/steipete/CodexBar";
|
||||||
|
license = lib.licenses.mit;
|
||||||
|
mainProgram = "codexbar";
|
||||||
|
platforms = [
|
||||||
|
"aarch64-linux"
|
||||||
|
"x86_64-linux"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -5,6 +5,14 @@
|
|||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
|
codexbar = import ./codexbar.nix { inherit lib pkgs; };
|
||||||
|
codexbarUsagePlugin = pkgs.runCommand "noctalia-codexbar-usage-plugin" { } ''
|
||||||
|
mkdir -p "$out"
|
||||||
|
cp ${./plugins/codexbar-usage/plugin.toml} "$out/plugin.toml"
|
||||||
|
substitute ${./plugins/codexbar-usage/usage.luau} "$out/usage.luau" \
|
||||||
|
--replace-fail "@codexbarPath@" "${lib.getExe codexbar}"
|
||||||
|
'';
|
||||||
|
|
||||||
wallpapers = pkgs.fetchFromGitHub {
|
wallpapers = pkgs.fetchFromGitHub {
|
||||||
owner = "moons-14";
|
owner = "moons-14";
|
||||||
repo = "wallpapers";
|
repo = "wallpapers";
|
||||||
@@ -21,6 +29,14 @@ let
|
|||||||
"cpu-graph"
|
"cpu-graph"
|
||||||
"ram"
|
"ram"
|
||||||
];
|
];
|
||||||
|
labwcSystemWidgets = [
|
||||||
|
"network"
|
||||||
|
"bluetooth"
|
||||||
|
"network-connection"
|
||||||
|
"spacer"
|
||||||
|
"cpu"
|
||||||
|
"ram"
|
||||||
|
];
|
||||||
centerWidgets = [
|
centerWidgets = [
|
||||||
"app-launcher"
|
"app-launcher"
|
||||||
"workspaces"
|
"workspaces"
|
||||||
@@ -45,7 +61,13 @@ let
|
|||||||
margin_ends = 0;
|
margin_ends = 0;
|
||||||
start = [ "taskbar" ];
|
start = [ "taskbar" ];
|
||||||
center = centerWidgets;
|
center = centerWidgets;
|
||||||
end = systemWidgets ++ [ "spacer" ] ++ endWidgets;
|
end =
|
||||||
|
labwcSystemWidgets
|
||||||
|
++ [
|
||||||
|
"moons/codexbar-usage:usage"
|
||||||
|
"spacer"
|
||||||
|
]
|
||||||
|
++ endWidgets;
|
||||||
};
|
};
|
||||||
|
|
||||||
dock = {
|
dock = {
|
||||||
@@ -56,6 +78,7 @@ let
|
|||||||
network.show_label = false;
|
network.show_label = false;
|
||||||
taskbar = {
|
taskbar = {
|
||||||
type = "taskbar";
|
type = "taskbar";
|
||||||
|
scale = 1.5;
|
||||||
pinned = [
|
pinned = [
|
||||||
"org.gnome.Nautilus"
|
"org.gnome.Nautilus"
|
||||||
"org.gnome.TextEditor"
|
"org.gnome.TextEditor"
|
||||||
@@ -64,10 +87,14 @@ let
|
|||||||
"code"
|
"code"
|
||||||
"dev.zed.Zed"
|
"dev.zed.Zed"
|
||||||
"codex"
|
"codex"
|
||||||
|
"vesktop"
|
||||||
];
|
];
|
||||||
group_by_workspace = false;
|
group_by_workspace = false;
|
||||||
show_window_title = false;
|
show_window_title = false;
|
||||||
|
pinned_opacity = 0.60;
|
||||||
|
inactive_opacity = 1.0;
|
||||||
};
|
};
|
||||||
|
input-volume.show_label = false;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -97,7 +124,10 @@ let
|
|||||||
};
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
home.packages = [ pkgs.networkmanagerapplet ];
|
home.packages = [
|
||||||
|
codexbar
|
||||||
|
pkgs.networkmanagerapplet
|
||||||
|
];
|
||||||
|
|
||||||
home.file = {
|
home.file = {
|
||||||
".face".source = ../../../images/avatar.jpg;
|
".face".source = ../../../images/avatar.jpg;
|
||||||
@@ -107,6 +137,11 @@ in
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
xdg.dataFile."noctalia/plugins/codexbar-usage" = {
|
||||||
|
source = codexbarUsagePlugin;
|
||||||
|
recursive = true;
|
||||||
|
};
|
||||||
|
|
||||||
programs.noctalia = {
|
programs.noctalia = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|
||||||
@@ -141,11 +176,16 @@ in
|
|||||||
center = centerWidgets;
|
center = centerWidgets;
|
||||||
end = [
|
end = [
|
||||||
"media"
|
"media"
|
||||||
|
"moons/codexbar-usage:usage"
|
||||||
"spacer"
|
"spacer"
|
||||||
]
|
]
|
||||||
++ endWidgets;
|
++ endWidgets;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
plugins = {
|
||||||
|
enabled = [ "moons/codexbar-usage" ];
|
||||||
|
};
|
||||||
|
|
||||||
widget = {
|
widget = {
|
||||||
app-launcher = {
|
app-launcher = {
|
||||||
type = "custom_button";
|
type = "custom_button";
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
id = "moons/codexbar-usage"
|
||||||
|
name = "CodexBar Usage"
|
||||||
|
version = "1.0.0"
|
||||||
|
plugin_api = 3
|
||||||
|
min_noctalia = "5.0.0"
|
||||||
|
author = "moons"
|
||||||
|
license = "MIT"
|
||||||
|
icon = "ai"
|
||||||
|
description = "Codex usage limits from the CodexBar CLI."
|
||||||
|
|
||||||
|
[[widget]]
|
||||||
|
id = "usage"
|
||||||
|
entry = "usage.luau"
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
local codexbar = "@codexbarPath@"
|
||||||
|
local refreshIntervalMs = 60000
|
||||||
|
local usagePage = "https://chatgpt.com/codex/settings/usage"
|
||||||
|
|
||||||
|
local refreshing = false
|
||||||
|
|
||||||
|
local function percent(value)
|
||||||
|
if type(value) ~= "number" then
|
||||||
|
return "—"
|
||||||
|
end
|
||||||
|
return tostring(math.floor(value + 0.5)) .. "%"
|
||||||
|
end
|
||||||
|
|
||||||
|
local function oneDecimal(value)
|
||||||
|
local tenths = math.floor(value * 10 + 0.5)
|
||||||
|
return tostring(math.floor(tenths / 10)) .. "." .. tostring(tenths % 10)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function unixSeconds(isoTime)
|
||||||
|
if type(isoTime) ~= "string" then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
local year, month, day, hour, minute, second = string.match(
|
||||||
|
isoTime,
|
||||||
|
"^(%d%d%d%d)%-(%d%d)%-(%d%d)T(%d%d):(%d%d):(%d%d)"
|
||||||
|
)
|
||||||
|
if year == nil then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
year = tonumber(year)
|
||||||
|
month = tonumber(month)
|
||||||
|
day = tonumber(day)
|
||||||
|
hour = tonumber(hour)
|
||||||
|
minute = tonumber(minute)
|
||||||
|
second = tonumber(second)
|
||||||
|
|
||||||
|
if month <= 2 then
|
||||||
|
year = year - 1
|
||||||
|
month = month + 12
|
||||||
|
end
|
||||||
|
|
||||||
|
local era = math.floor(year / 400)
|
||||||
|
local yearOfEra = year - era * 400
|
||||||
|
local dayOfYear = math.floor((153 * (month - 3) + 2) / 5) + day - 1
|
||||||
|
local dayOfEra = yearOfEra * 365 + math.floor(yearOfEra / 4) - math.floor(yearOfEra / 100) + dayOfYear
|
||||||
|
local daysSinceEpoch = era * 146097 + dayOfEra - 719468
|
||||||
|
|
||||||
|
return daysSinceEpoch * 86400 + hour * 3600 + minute * 60 + second
|
||||||
|
end
|
||||||
|
|
||||||
|
local function resetRemaining(resetsAt, resetDescription)
|
||||||
|
local resetSeconds = unixSeconds(resetsAt)
|
||||||
|
if resetSeconds == nil then
|
||||||
|
return resetDescription or "—"
|
||||||
|
end
|
||||||
|
|
||||||
|
local remaining = math.max(0, resetSeconds - math.floor(noctalia.nowMs() / 1000))
|
||||||
|
local days = math.floor(remaining / 86400)
|
||||||
|
if days > 0 then
|
||||||
|
return tostring(days) .. "d" .. oneDecimal((remaining - days * 86400) / 3600) .. "h"
|
||||||
|
end
|
||||||
|
|
||||||
|
return oneDecimal(remaining / 3600) .. "h"
|
||||||
|
end
|
||||||
|
|
||||||
|
local function showError(message)
|
||||||
|
barWidget.setGlyph("alert-circle")
|
||||||
|
barWidget.setText("Codex: unavailable")
|
||||||
|
barWidget.setTooltip("CodexBar: " .. message)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function showUsage(item)
|
||||||
|
local usage = item.usage or {}
|
||||||
|
local primary = usage.primary or {}
|
||||||
|
local secondary = usage.secondary or {}
|
||||||
|
local primaryReset = resetRemaining(primary.resetsAt, primary.resetDescription)
|
||||||
|
local secondaryReset = resetRemaining(secondary.resetsAt, secondary.resetDescription)
|
||||||
|
|
||||||
|
barWidget.setGlyph("ai")
|
||||||
|
barWidget.setText(
|
||||||
|
"5h:"
|
||||||
|
.. percent(primary.usedPercent)
|
||||||
|
.. "("
|
||||||
|
.. primaryReset
|
||||||
|
.. ") - 7d:"
|
||||||
|
.. percent(secondary.usedPercent)
|
||||||
|
.. "("
|
||||||
|
.. secondaryReset
|
||||||
|
.. ")"
|
||||||
|
)
|
||||||
|
barWidget.setTooltip({
|
||||||
|
{ "5-hour usage", percent(primary.usedPercent) .. " · resets in " .. primaryReset },
|
||||||
|
{ "Weekly usage", percent(secondary.usedPercent) .. " · resets in " .. secondaryReset },
|
||||||
|
{ "Click", "Open Codex usage settings" },
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
local function refresh()
|
||||||
|
if refreshing then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
refreshing = true
|
||||||
|
noctalia.runAsync(
|
||||||
|
codexbar .. " usage --provider codex --source cli --format json",
|
||||||
|
function(result)
|
||||||
|
refreshing = false
|
||||||
|
|
||||||
|
if result.exitCode ~= 0 then
|
||||||
|
showError(result.stderr ~= "" and result.stderr or "codexbar exited " .. tostring(result.exitCode))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local data, error = noctalia.json.decode(result.stdout)
|
||||||
|
if data == nil then
|
||||||
|
showError("invalid CodexBar JSON: " .. (error or "unknown error"))
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local item = data[1] or data
|
||||||
|
if item.error ~= nil then
|
||||||
|
showError(item.error.message or "CodexBar returned an error")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
showUsage(item)
|
||||||
|
end,
|
||||||
|
30000
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
function update()
|
||||||
|
noctalia.setUpdateInterval(refreshIntervalMs)
|
||||||
|
refresh()
|
||||||
|
end
|
||||||
|
|
||||||
|
function onClick()
|
||||||
|
noctalia.runAsync("xdg-open " .. usagePage)
|
||||||
|
end
|
||||||
|
|
||||||
|
function onRightClick()
|
||||||
|
refresh()
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user