mirror of
https://github.com/zoriya/flake.git
synced 2025-12-05 22:26:21 +00:00
Switch to noctalia
This commit is contained in:
21
flake.lock
generated
21
flake.lock
generated
@@ -236,6 +236,26 @@
|
|||||||
"type": "indirect"
|
"type": "indirect"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"noctalia": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1763904998,
|
||||||
|
"narHash": "sha256-MMJtTkftqGLSShrPtObN/ZB9eveq6JQDzrdXvQ8UwGY=",
|
||||||
|
"owner": "zoriya",
|
||||||
|
"repo": "noctalia-shell",
|
||||||
|
"rev": "8975e4f979bf7ef2c7a62446761546899c336dd8",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "zoriya",
|
||||||
|
"repo": "noctalia-shell",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"home-manager": "home-manager",
|
"home-manager": "home-manager",
|
||||||
@@ -247,6 +267,7 @@
|
|||||||
"nixos-hardware": "nixos-hardware",
|
"nixos-hardware": "nixos-hardware",
|
||||||
"nixos-wsl": "nixos-wsl",
|
"nixos-wsl": "nixos-wsl",
|
||||||
"nixpkgs": "nixpkgs_2",
|
"nixpkgs": "nixpkgs_2",
|
||||||
|
"noctalia": "noctalia",
|
||||||
"tmux": "tmux",
|
"tmux": "tmux",
|
||||||
"zen-browser": "zen-browser"
|
"zen-browser": "zen-browser"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,10 @@
|
|||||||
url = "github:youwen5/zen-browser-flake";
|
url = "github:youwen5/zen-browser-flake";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
|
noctalia = {
|
||||||
|
url = "github:zoriya/noctalia-shell";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
# use tmux's master for mode 2031
|
# use tmux's master for mode 2031
|
||||||
tmux = {
|
tmux = {
|
||||||
url = "github:tmux/tmux";
|
url = "github:tmux/tmux";
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
root = true
|
|
||||||
|
|
||||||
[*]
|
|
||||||
charset = utf-8
|
|
||||||
end_of_line = lf
|
|
||||||
trim_trailing_whitespace = true
|
|
||||||
insert_final_newline = true
|
|
||||||
indent_style = tab
|
|
||||||
indent_size = tab
|
|
||||||
max_line_length = 120
|
|
||||||
|
|
||||||
[{*.yaml,*.yml}]
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 2
|
|
||||||
4
modules/wm/ags/.gitignore
vendored
4
modules/wm/ags/.gitignore
vendored
@@ -1,4 +0,0 @@
|
|||||||
node_modules
|
|
||||||
package-lock.json
|
|
||||||
weather_key
|
|
||||||
settings.json
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import Gtk from "gi://Gtk?version=3.0";
|
|
||||||
import Gdk from "gi://Gdk";
|
|
||||||
|
|
||||||
import { Bar } from "./layouts/bar.js";
|
|
||||||
import { Notifications } from "./layouts/notifications.js";
|
|
||||||
import { OSD } from "./layouts/osd.js";
|
|
||||||
import { Quicksettings } from "./layouts/quicksettings.js";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Array<(monitor: number) => Gtk.Window>} widgets
|
|
||||||
*/
|
|
||||||
export function forMonitors(widgets) {
|
|
||||||
const display = Gdk.Display.get_default();
|
|
||||||
|
|
||||||
display?.connect("monitor-added", (disp, gdkmonitor) => {
|
|
||||||
let monitor = 0;
|
|
||||||
for (let i = 0; i < display.get_n_monitors(); i++) {
|
|
||||||
if (gdkmonitor === display.get_monitor(i)) {
|
|
||||||
monitor = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
widgets.forEach((win) => App.addWindow(win(monitor)));
|
|
||||||
});
|
|
||||||
|
|
||||||
display?.connect("monitor-removed", (disp, monitor) => {
|
|
||||||
App.windows.forEach((win) => {
|
|
||||||
// @ts-ignore
|
|
||||||
if (win.gdkmonitor === monitor) App.removeWindow(win);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const n = display?.get_n_monitors() || 1;
|
|
||||||
return Array.from({ length: n }, (_, i) => i).flatMap((mon) =>
|
|
||||||
widgets.map((x) => x(mon)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
App.config({
|
|
||||||
closeWindowDelay: {
|
|
||||||
quicksettings: 300,
|
|
||||||
notifications: 200,
|
|
||||||
osd: 300,
|
|
||||||
},
|
|
||||||
style: `${App.configDir}/style.css`,
|
|
||||||
windows: [...forMonitors([Bar]), Quicksettings(), Notifications(), OSD()],
|
|
||||||
});
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
#! /usr/bin/env python3
|
|
||||||
|
|
||||||
from material_color_utilities_python import *
|
|
||||||
from PIL import Image
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import hashlib
|
|
||||||
|
|
||||||
CACHE_DIR = os.path.expanduser("~/.cache/ags/tempcolors")
|
|
||||||
|
|
||||||
def cache_and_get_colors(file_path):
|
|
||||||
file_hash = hashlib.sha256(file_path.encode('utf-8')).hexdigest()
|
|
||||||
cache_path = os.path.join(CACHE_DIR, file_hash[:2], file_hash[2:])
|
|
||||||
|
|
||||||
os.makedirs(cache_path, exist_ok=True)
|
|
||||||
|
|
||||||
colors_path = os.path.join(cache_path, "colors.json")
|
|
||||||
if os.path.exists(colors_path):
|
|
||||||
with open(colors_path, "r") as f:
|
|
||||||
parsed_colors = json.load(f)
|
|
||||||
elif not os.path.exists(file_path):
|
|
||||||
return None
|
|
||||||
img = Image.open(file_path)
|
|
||||||
basewidth = 8
|
|
||||||
wpercent = (basewidth/float(img.size[0]))
|
|
||||||
hsize = int((float(img.size[1])*float(wpercent)))
|
|
||||||
img = img.resize((basewidth, hsize), Image.Resampling.LANCZOS)
|
|
||||||
theme = themeFromImage(img)
|
|
||||||
|
|
||||||
scheme = theme.get("schemes")
|
|
||||||
|
|
||||||
parsed_colors = {
|
|
||||||
"primary": hexFromArgb(scheme.get("dark").primary),
|
|
||||||
"onPrimary": hexFromArgb(scheme.get("dark").onPrimary),
|
|
||||||
"background": hexFromArgb(scheme.get("dark").background),
|
|
||||||
"onBackground": hexFromArgb(scheme.get("dark").onBackground),
|
|
||||||
}
|
|
||||||
|
|
||||||
with open(colors_path, "w") as f:
|
|
||||||
json.dump(parsed_colors, f)
|
|
||||||
|
|
||||||
return parsed_colors
|
|
||||||
|
|
||||||
fn = sys.argv[1]
|
|
||||||
|
|
||||||
parsed_colors = cache_and_get_colors(fn)
|
|
||||||
|
|
||||||
print(json.dumps(parsed_colors, indent=4))
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
{pkgs, ...}: let
|
|
||||||
covercolors = pkgs.stdenv.mkDerivation {
|
|
||||||
name = "covercolors";
|
|
||||||
dontUnpack = true;
|
|
||||||
propagatedBuildInputs = [
|
|
||||||
(pkgs.python3.withPackages (pyPkgs:
|
|
||||||
with pyPkgs; [
|
|
||||||
material-color-utilities
|
|
||||||
pillow
|
|
||||||
]))
|
|
||||||
];
|
|
||||||
installPhase = "install -Dm755 ${./covercolors.py} $out/bin/covercolors";
|
|
||||||
};
|
|
||||||
# systemdTarget = "graphical-session.target";
|
|
||||||
ags = pkgs.ags_1.overrideAttrs (_: prev: {
|
|
||||||
buildInputs =
|
|
||||||
prev.buildInputs
|
|
||||||
++ [
|
|
||||||
pkgs.libdbusmenu-gtk3
|
|
||||||
];
|
|
||||||
});
|
|
||||||
in {
|
|
||||||
home.packages = with pkgs; [
|
|
||||||
ags
|
|
||||||
# TODO: Find a way to add this for ags only
|
|
||||||
covercolors
|
|
||||||
brightnessctl
|
|
||||||
blueberry
|
|
||||||
];
|
|
||||||
# systemd.user.services.ags = {
|
|
||||||
# Unit = {
|
|
||||||
# Description = " A customizable and extensible shell ";
|
|
||||||
# PartOf = systemdTarget;
|
|
||||||
# Requires = systemdTarget;
|
|
||||||
# After = systemdTarget;
|
|
||||||
# };
|
|
||||||
#
|
|
||||||
# Service = {
|
|
||||||
# Type = "simple";
|
|
||||||
# ExecStart = "${ags}/bin/ags";
|
|
||||||
# Restart = "always";
|
|
||||||
# };
|
|
||||||
#
|
|
||||||
# Install = {WantedBy = [systemdTarget];};
|
|
||||||
# };
|
|
||||||
|
|
||||||
xdg.configFile."ags".source = ./.;
|
|
||||||
}
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
import { Clock } from "../modules/clock.js";
|
|
||||||
import * as wm from "../modules/wm.js";
|
|
||||||
import * as audio from "../modules/audio.js";
|
|
||||||
import * as network from "../modules/network.js";
|
|
||||||
import * as bluetooth from "../modules/bluetooth.js";
|
|
||||||
import * as battery from "../modules/battery.js";
|
|
||||||
import * as notifications from "../modules/notifications.js";
|
|
||||||
import * as mpris from "../modules/mpris.js";
|
|
||||||
|
|
||||||
/**
|
|
||||||
*@param {number} monitor
|
|
||||||
*/
|
|
||||||
export const Bar = (monitor) =>
|
|
||||||
Widget.Window({
|
|
||||||
monitor,
|
|
||||||
name: `bar${monitor}`,
|
|
||||||
className: "transparent",
|
|
||||||
exclusivity: "exclusive",
|
|
||||||
anchor: ["top", "left", "right"],
|
|
||||||
layer: "bottom",
|
|
||||||
child: Widget.CenterBox({
|
|
||||||
// startWidget: Widget.Box({
|
|
||||||
// children: [
|
|
||||||
// wm.Tags({
|
|
||||||
// monitor,
|
|
||||||
// labels: ["一", "二", "三", "四", "五", "六", "七", "八", "九"],
|
|
||||||
// }),
|
|
||||||
// wm.Layout({ monitor }),
|
|
||||||
// wm.ClientLabel({ monitor }),
|
|
||||||
// ],
|
|
||||||
// }),
|
|
||||||
centerWidget: Widget.Box({
|
|
||||||
hpack: "center",
|
|
||||||
children: [
|
|
||||||
Widget.Button({
|
|
||||||
css: "min-width: 200px;",
|
|
||||||
onClicked: () => App.toggleWindow("notifications"),
|
|
||||||
child: notifications.Indicator({
|
|
||||||
hexpand: true,
|
|
||||||
hpack: "center",
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
endWidget: Widget.Box({
|
|
||||||
hpack: "end",
|
|
||||||
children: [
|
|
||||||
Widget.Box({
|
|
||||||
className: "module",
|
|
||||||
css: "margin-right: 48px",
|
|
||||||
visible: mpris.activePlayer.bind().as((x) => !!x),
|
|
||||||
children: mpris.activePlayer
|
|
||||||
.bind()
|
|
||||||
.as((player) => (player ? [mpris.LinePlayer({ player })] : [])),
|
|
||||||
}),
|
|
||||||
Widget.Button({
|
|
||||||
onClicked: () => App.toggleWindow("quicksettings"),
|
|
||||||
className: "module quicksettings",
|
|
||||||
child: Widget.Box({
|
|
||||||
children: [
|
|
||||||
audio.MicrophoneIndicator({
|
|
||||||
className: "qs-item",
|
|
||||||
}),
|
|
||||||
notifications.DNDIndicator({
|
|
||||||
className: "qs-item",
|
|
||||||
}),
|
|
||||||
network.Indicator({ className: "qs-item" }),
|
|
||||||
audio.VolumeIndicator({ className: "qs-item" }),
|
|
||||||
bluetooth.Indicator({
|
|
||||||
hideIfDisabled: true,
|
|
||||||
className: "qs-item",
|
|
||||||
}),
|
|
||||||
battery.Indicator({ className: "qs-item" }),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
}).hook(App, (self, win, visible) => {
|
|
||||||
self.toggleClassName("active", win === "quicksettings" && visible);
|
|
||||||
}),
|
|
||||||
Clock({ format: "%a %d %b", className: "module bold" }),
|
|
||||||
Clock({
|
|
||||||
format: "%H:%M",
|
|
||||||
className: "module accent bold",
|
|
||||||
css: "margin-right: 0px",
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
import PopupWindow from "../misc/popup.js";
|
|
||||||
import * as notifications from "../modules/notifications.js";
|
|
||||||
|
|
||||||
const notificationsService = await Service.import("notifications");
|
|
||||||
|
|
||||||
const Header = () =>
|
|
||||||
Widget.Box({
|
|
||||||
hexpand: true,
|
|
||||||
css: "margin-bottom: 40px",
|
|
||||||
children: [
|
|
||||||
notifications.DNDToggle({
|
|
||||||
className: "surface p10 round",
|
|
||||||
css: `
|
|
||||||
min-width: 24px;
|
|
||||||
min-height: 24px;
|
|
||||||
`,
|
|
||||||
hpack: "start",
|
|
||||||
hexpand: true,
|
|
||||||
}),
|
|
||||||
notifications.ClearButton({
|
|
||||||
hpack: "end",
|
|
||||||
hexpand: true,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
export const Notifications = () =>
|
|
||||||
PopupWindow({
|
|
||||||
name: "notifications",
|
|
||||||
exclusivity: "exclusive",
|
|
||||||
transition: "slide_down",
|
|
||||||
layout: "top-center",
|
|
||||||
duration: 300,
|
|
||||||
child: Widget.Box({
|
|
||||||
vertical: true,
|
|
||||||
className: "bgcont",
|
|
||||||
css: `
|
|
||||||
min-width: 600px;
|
|
||||||
margin: 10px;
|
|
||||||
padding: 12px;
|
|
||||||
border-radius: 20px;
|
|
||||||
`,
|
|
||||||
children:
|
|
||||||
/** @type {any} */
|
|
||||||
(
|
|
||||||
notificationsService
|
|
||||||
.bind("notifications")
|
|
||||||
.as((x) =>
|
|
||||||
x.length > 0
|
|
||||||
? [Header(), notifications.List({})]
|
|
||||||
: [Header(), notifications.Placeholder({})],
|
|
||||||
)
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
import { getIcon } from "../modules/audio.js";
|
|
||||||
import brightness from "../services/brightness.js";
|
|
||||||
|
|
||||||
const audio = await Service.import("audio");
|
|
||||||
|
|
||||||
const DELAY = 1000;
|
|
||||||
|
|
||||||
function OnScreenProgress() {
|
|
||||||
const indicator = Widget.Icon({
|
|
||||||
vpack: "start",
|
|
||||||
hpack: "center",
|
|
||||||
size: 30,
|
|
||||||
css: "padding-right: 12px;",
|
|
||||||
});
|
|
||||||
const progress = Widget.Slider({
|
|
||||||
drawValue: false,
|
|
||||||
hexpand: true,
|
|
||||||
});
|
|
||||||
const revealer = Widget.Revealer({
|
|
||||||
transition: "crossfade",
|
|
||||||
css: "opacity: 0",
|
|
||||||
revealChild: true,
|
|
||||||
vpack: "center",
|
|
||||||
hpack: "center",
|
|
||||||
child: Widget.Box({
|
|
||||||
vpack: "center",
|
|
||||||
hpack: "center",
|
|
||||||
className: "osd bgcount",
|
|
||||||
css: "padding: 20px;",
|
|
||||||
children: [indicator, progress],
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
// Prevent OSD to be shown when starting ags.
|
|
||||||
Utils.timeout(DELAY * 2, () => {
|
|
||||||
revealer.css = "opacity: 1";
|
|
||||||
});
|
|
||||||
|
|
||||||
let count = 0;
|
|
||||||
/**
|
|
||||||
* @param {number} value
|
|
||||||
* @param {string} icon
|
|
||||||
*/
|
|
||||||
function show(value, icon) {
|
|
||||||
revealer.reveal_child = true;
|
|
||||||
indicator.icon = icon;
|
|
||||||
progress.value = value;
|
|
||||||
count++;
|
|
||||||
Utils.timeout(DELAY, () => {
|
|
||||||
count--;
|
|
||||||
if (count === 0) revealer.reveal_child = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return revealer
|
|
||||||
.hook(
|
|
||||||
brightness,
|
|
||||||
() => show(brightness.screen, "display-brightness-symbolic"),
|
|
||||||
"notify::screen",
|
|
||||||
)
|
|
||||||
.hook(
|
|
||||||
audio.speaker,
|
|
||||||
() => show(audio.speaker.volume, getIcon(audio.speaker.volume * 100)),
|
|
||||||
"notify::volume",
|
|
||||||
)
|
|
||||||
.hook(
|
|
||||||
audio.speaker,
|
|
||||||
() =>
|
|
||||||
show(
|
|
||||||
audio.speaker.is_muted ? 0 : audio.speaker.volume,
|
|
||||||
audio.speaker.is_muted
|
|
||||||
? "audio-volume-muted-symbolic"
|
|
||||||
: getIcon(audio.speaker.volume * 100),
|
|
||||||
),
|
|
||||||
"notify::is-muted",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const OSD = () =>
|
|
||||||
Widget.Window({
|
|
||||||
name: "osd",
|
|
||||||
className: "indicator",
|
|
||||||
layer: "overlay",
|
|
||||||
clickThrough: true,
|
|
||||||
anchor: ["bottom"],
|
|
||||||
child: OnScreenProgress(),
|
|
||||||
});
|
|
||||||
@@ -1,160 +0,0 @@
|
|||||||
import Gtk from "gi://Gtk?version=3.0";
|
|
||||||
import * as audio from "../modules/audio.js";
|
|
||||||
import * as brightness from "../modules/brightness.js";
|
|
||||||
import * as network from "../modules/network.js";
|
|
||||||
import * as bluetooth from "../modules/bluetooth.js";
|
|
||||||
import * as darkmode from "../modules/darkmode.js";
|
|
||||||
import * as powerprofile from "../modules/powerprofile.js";
|
|
||||||
// import * as nightmode from "../modules/nightmode.js";
|
|
||||||
import * as mpris from "../modules/mpris.js";
|
|
||||||
import * as systray from "../modules/systray.js";
|
|
||||||
import PopupWindow from "../misc/popup.js";
|
|
||||||
import { opened, Menu } from "../misc/menu.js";
|
|
||||||
|
|
||||||
const mprisService = await Service.import("mpris");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Array<Gtk.Widget>} toggles
|
|
||||||
* @param {Array<Gtk.Widget>} menus
|
|
||||||
*/
|
|
||||||
const Row = (toggles = [], menus = []) =>
|
|
||||||
Widget.Box({
|
|
||||||
vertical: true,
|
|
||||||
children: [
|
|
||||||
Widget.Box({
|
|
||||||
homogeneous: true,
|
|
||||||
children: toggles,
|
|
||||||
}),
|
|
||||||
...menus,
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
const Header = () =>
|
|
||||||
Widget.Box({
|
|
||||||
vertical: true,
|
|
||||||
children: [
|
|
||||||
Widget.Box({
|
|
||||||
css: "margin-bottom: 12px",
|
|
||||||
children: [
|
|
||||||
Widget.Box({
|
|
||||||
className: "avatar",
|
|
||||||
css: `background-image: url("/home/${Utils.USER}/.face");`,
|
|
||||||
}),
|
|
||||||
Widget.Box({ hexpand: true }),
|
|
||||||
Widget.Button({
|
|
||||||
child: Widget.Icon("emblem-system-symbolic"),
|
|
||||||
onClicked: () => {
|
|
||||||
Utils.execAsync("gnome-control-center");
|
|
||||||
App.closeWindow("quicksettings");
|
|
||||||
},
|
|
||||||
vpack: "center",
|
|
||||||
className: "surface sys-button",
|
|
||||||
}),
|
|
||||||
Widget.Button({
|
|
||||||
child: Widget.Icon("system-log-out-symbolic"),
|
|
||||||
onClicked: () => {
|
|
||||||
opened.value = opened.value === "sleep" ? "" : "sleep";
|
|
||||||
},
|
|
||||||
vpack: "center",
|
|
||||||
css: "margin: 12px",
|
|
||||||
className: "surface sys-button",
|
|
||||||
}),
|
|
||||||
Widget.Button({
|
|
||||||
child: Widget.Icon("system-shutdown-symbolic"),
|
|
||||||
onClicked: () => {
|
|
||||||
opened.value = opened.value === "shutdown" ? "" : "shutdown";
|
|
||||||
},
|
|
||||||
vpack: "center",
|
|
||||||
className: "surface sys-button",
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
VerificationMenu({
|
|
||||||
name: "sleep",
|
|
||||||
icon: "system-log-out-symbolic",
|
|
||||||
title: "Hybernate?",
|
|
||||||
command: "systemctl suspend --now",
|
|
||||||
}),
|
|
||||||
VerificationMenu({
|
|
||||||
name: "shutdown",
|
|
||||||
icon: "system-shutdown-symbolic",
|
|
||||||
title: "Shutdown?",
|
|
||||||
command: "shutdown now",
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @param {{
|
|
||||||
* name: string,
|
|
||||||
* icon: string,
|
|
||||||
* title: string,
|
|
||||||
* command: string,
|
|
||||||
* }} props */
|
|
||||||
const VerificationMenu = ({ name, icon, title, command }) =>
|
|
||||||
Menu({
|
|
||||||
name,
|
|
||||||
icon: Widget.Icon(icon),
|
|
||||||
title: title,
|
|
||||||
content: [
|
|
||||||
Widget.Button({
|
|
||||||
onClicked: () => {
|
|
||||||
opened.value = "";
|
|
||||||
App.closeWindow("quicksettings");
|
|
||||||
Utils.execAsync(command);
|
|
||||||
},
|
|
||||||
child: Widget.Label({
|
|
||||||
label: "Yes",
|
|
||||||
hpack: "start",
|
|
||||||
css: "margin-left: 12px",
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
Widget.Button({
|
|
||||||
onClicked: () => {
|
|
||||||
opened.value = "";
|
|
||||||
},
|
|
||||||
child: Widget.Label({
|
|
||||||
label: "No",
|
|
||||||
hpack: "start",
|
|
||||||
css: "margin-left: 12px",
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
export const Quicksettings = () =>
|
|
||||||
PopupWindow({
|
|
||||||
name: "quicksettings",
|
|
||||||
exclusivity: "exclusive",
|
|
||||||
transition: "slide_down",
|
|
||||||
layout: "top-right",
|
|
||||||
duration: 300,
|
|
||||||
child: Widget.Box({
|
|
||||||
vertical: true,
|
|
||||||
className: "bgcont qs-container",
|
|
||||||
children: [
|
|
||||||
Header(),
|
|
||||||
Row(
|
|
||||||
[audio.Volume({ type: "speaker" })],
|
|
||||||
[audio.SinkSelector({}), audio.AppMixer({})],
|
|
||||||
),
|
|
||||||
brightness.Brightness({}),
|
|
||||||
Row(
|
|
||||||
[network.Toggle({}), bluetooth.Toggle({})],
|
|
||||||
[network.Selection({}), bluetooth.Selection({})],
|
|
||||||
),
|
|
||||||
Widget.Box({
|
|
||||||
homogeneous: true,
|
|
||||||
children: [darkmode.Toggle(), audio.MuteToggle({})],
|
|
||||||
}),
|
|
||||||
Row(
|
|
||||||
[systray.Toggle({}), powerprofile.Toggle({})],
|
|
||||||
[systray.Selection({}), powerprofile.Selection({})],
|
|
||||||
),
|
|
||||||
Widget.Box({
|
|
||||||
children: mpris.activePlayer
|
|
||||||
.bind()
|
|
||||||
.as((player) => player ? [mpris.MprisPlayer({ player })] : []),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
@@ -1,166 +0,0 @@
|
|||||||
const { Gtk } = imports.gi;
|
|
||||||
const Lang = imports.lang;
|
|
||||||
import * as Utils from "resource:///com/github/Aylur/ags/utils.js";
|
|
||||||
|
|
||||||
// -- Styling --
|
|
||||||
// min-height for diameter
|
|
||||||
// min-width for trough stroke
|
|
||||||
// padding for space between trough and progress
|
|
||||||
// margin for space between widget and parent
|
|
||||||
// background-color for trough color
|
|
||||||
// color for progress color
|
|
||||||
// -- Usage --
|
|
||||||
// font size for progress value (0-100px) (hacky i know, but i want animations)
|
|
||||||
export const AnimatedCircProg = ({
|
|
||||||
initFrom = 0,
|
|
||||||
initTo = 0,
|
|
||||||
initAnimTime = 2900,
|
|
||||||
initAnimPoints = 1,
|
|
||||||
extraSetup = () => {},
|
|
||||||
...rest
|
|
||||||
}) =>
|
|
||||||
Widget.DrawingArea({
|
|
||||||
...rest,
|
|
||||||
css: `${
|
|
||||||
initFrom !== initTo
|
|
||||||
? `font-size: ${initFrom}px; transition: ${initAnimTime}ms linear;`
|
|
||||||
: ""
|
|
||||||
}`,
|
|
||||||
setup: (area) => {
|
|
||||||
const styleContext = area.get_style_context();
|
|
||||||
const width = styleContext.get_property(
|
|
||||||
"min-height",
|
|
||||||
Gtk.StateFlags.NORMAL,
|
|
||||||
);
|
|
||||||
const height = styleContext.get_property(
|
|
||||||
"min-height",
|
|
||||||
Gtk.StateFlags.NORMAL,
|
|
||||||
);
|
|
||||||
const padding = styleContext.get_padding(Gtk.StateFlags.NORMAL).left;
|
|
||||||
const marginLeft = styleContext.get_margin(Gtk.StateFlags.NORMAL).left;
|
|
||||||
const marginRight = styleContext.get_margin(Gtk.StateFlags.NORMAL).right;
|
|
||||||
const marginTop = styleContext.get_margin(Gtk.StateFlags.NORMAL).top;
|
|
||||||
const marginBottom = styleContext.get_margin(
|
|
||||||
Gtk.StateFlags.NORMAL,
|
|
||||||
).bottom;
|
|
||||||
area.set_size_request(
|
|
||||||
width + marginLeft + marginRight,
|
|
||||||
height + marginTop + marginBottom,
|
|
||||||
);
|
|
||||||
area.connect(
|
|
||||||
"draw",
|
|
||||||
Lang.bind(area, (area, cr) => {
|
|
||||||
const styleContext = area.get_style_context();
|
|
||||||
const width = styleContext.get_property(
|
|
||||||
"min-height",
|
|
||||||
Gtk.StateFlags.NORMAL,
|
|
||||||
);
|
|
||||||
const height = styleContext.get_property(
|
|
||||||
"min-height",
|
|
||||||
Gtk.StateFlags.NORMAL,
|
|
||||||
);
|
|
||||||
const padding = styleContext.get_padding(Gtk.StateFlags.NORMAL).left;
|
|
||||||
const marginLeft = styleContext.get_margin(
|
|
||||||
Gtk.StateFlags.NORMAL,
|
|
||||||
).left;
|
|
||||||
const marginRight = styleContext.get_margin(
|
|
||||||
Gtk.StateFlags.NORMAL,
|
|
||||||
).right;
|
|
||||||
const marginTop = styleContext.get_margin(Gtk.StateFlags.NORMAL).top;
|
|
||||||
const marginBottom = styleContext.get_margin(
|
|
||||||
Gtk.StateFlags.NORMAL,
|
|
||||||
).bottom;
|
|
||||||
area.set_size_request(
|
|
||||||
width + marginLeft + marginRight,
|
|
||||||
height + marginTop + marginBottom,
|
|
||||||
);
|
|
||||||
|
|
||||||
const progressValue =
|
|
||||||
styleContext.get_property("font-size", Gtk.StateFlags.NORMAL) /
|
|
||||||
100.0;
|
|
||||||
|
|
||||||
const bg_stroke = styleContext.get_property(
|
|
||||||
"min-width",
|
|
||||||
Gtk.StateFlags.NORMAL,
|
|
||||||
);
|
|
||||||
const fg_stroke = bg_stroke - padding;
|
|
||||||
const radius =
|
|
||||||
Math.min(width, height) / 2.0 -
|
|
||||||
Math.max(bg_stroke, fg_stroke) / 2.0;
|
|
||||||
const center_x = width / 2.0 + marginLeft;
|
|
||||||
const center_y = height / 2.0 + marginTop;
|
|
||||||
const start_angle = -Math.PI / 2.0;
|
|
||||||
const end_angle = start_angle + 2 * Math.PI * progressValue;
|
|
||||||
const start_x = center_x + Math.cos(start_angle) * radius;
|
|
||||||
const start_y = center_y + Math.sin(start_angle) * radius;
|
|
||||||
const end_x = center_x + Math.cos(end_angle) * radius;
|
|
||||||
const end_y = center_y + Math.sin(end_angle) * radius;
|
|
||||||
|
|
||||||
// Draw background
|
|
||||||
const background_color = styleContext.get_property(
|
|
||||||
"background-color",
|
|
||||||
Gtk.StateFlags.NORMAL,
|
|
||||||
);
|
|
||||||
cr.setSourceRGBA(
|
|
||||||
background_color.red,
|
|
||||||
background_color.green,
|
|
||||||
background_color.blue,
|
|
||||||
background_color.alpha,
|
|
||||||
);
|
|
||||||
cr.arc(center_x, center_y, radius, 0, 2 * Math.PI);
|
|
||||||
cr.setLineWidth(bg_stroke);
|
|
||||||
cr.stroke();
|
|
||||||
|
|
||||||
if (progressValue == 0) return;
|
|
||||||
|
|
||||||
// Draw progress
|
|
||||||
const color = styleContext.get_property(
|
|
||||||
"color",
|
|
||||||
Gtk.StateFlags.NORMAL,
|
|
||||||
);
|
|
||||||
cr.setSourceRGBA(color.red, color.green, color.blue, color.alpha);
|
|
||||||
cr.arc(center_x, center_y, radius, start_angle, end_angle);
|
|
||||||
cr.setLineWidth(fg_stroke);
|
|
||||||
cr.stroke();
|
|
||||||
|
|
||||||
// Draw rounded ends for progress arcs
|
|
||||||
cr.setLineWidth(0);
|
|
||||||
cr.arc(start_x, start_y, fg_stroke / 2, 0, 0 - 0.01);
|
|
||||||
cr.fill();
|
|
||||||
cr.arc(end_x, end_y, fg_stroke / 2, 0, 0 - 0.01);
|
|
||||||
cr.fill();
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Init animation
|
|
||||||
if (initFrom != initTo) {
|
|
||||||
area.css = `font-size: ${initFrom}px; transition: ${initAnimTime}ms linear;`;
|
|
||||||
Utils.timeout(
|
|
||||||
20,
|
|
||||||
() => {
|
|
||||||
area.css = `font-size: ${initTo}px;`;
|
|
||||||
},
|
|
||||||
area,
|
|
||||||
);
|
|
||||||
const transitionDistance = initTo - initFrom;
|
|
||||||
const oneStep = initAnimTime / initAnimPoints;
|
|
||||||
area.css = `
|
|
||||||
font-size: ${initFrom}px;
|
|
||||||
transition: ${oneStep}ms linear;
|
|
||||||
`;
|
|
||||||
for (let i = 0; i < initAnimPoints; i++) {
|
|
||||||
Utils.timeout(Math.max(10, i * oneStep), () => {
|
|
||||||
if (!area) return;
|
|
||||||
area.css = `${
|
|
||||||
initFrom != initTo
|
|
||||||
? "font-size: " +
|
|
||||||
(initFrom + (transitionDistance / initAnimPoints) * (i + 1)) +
|
|
||||||
"px;"
|
|
||||||
: ""
|
|
||||||
}`;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else area.css = "font-size: 0px;";
|
|
||||||
extraSetup(area);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -1,186 +0,0 @@
|
|||||||
import GObject from "gi://GObject?version=2.0";
|
|
||||||
import Gtk from "gi://Gtk?version=3.0";
|
|
||||||
|
|
||||||
export const opened = Variable("");
|
|
||||||
App.connect("window-toggled", (_, name, visible) => {
|
|
||||||
if (name === "quicksettings" && !visible)
|
|
||||||
Utils.timeout(500, () => {
|
|
||||||
opened.value = "";
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {{
|
|
||||||
* name: string,
|
|
||||||
* activate?: false | (() => void),
|
|
||||||
* } & import("../types/widgets/button").ButtonProps} props
|
|
||||||
*/
|
|
||||||
export const Arrow = ({ name, activate, ...props }) => {
|
|
||||||
let deg = 0;
|
|
||||||
let iconOpened = false;
|
|
||||||
const icon = Widget.Icon("pan-end-symbolic").hook(opened, () => {
|
|
||||||
if (
|
|
||||||
(opened.value === name && !iconOpened) ||
|
|
||||||
(opened.value !== name && iconOpened)
|
|
||||||
) {
|
|
||||||
const step = opened.value === name ? 10 : -10;
|
|
||||||
iconOpened = !iconOpened;
|
|
||||||
for (let i = 0; i < 9; ++i) {
|
|
||||||
Utils.timeout(15 * i, () => {
|
|
||||||
deg += step;
|
|
||||||
icon.setCss(`-gtk-icon-transform: rotate(${deg}deg);`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return Widget.Button({
|
|
||||||
child: icon,
|
|
||||||
className: "qs-icon",
|
|
||||||
onClicked: () => {
|
|
||||||
opened.value = opened.value === name ? "" : name;
|
|
||||||
if (typeof activate === "function") activate();
|
|
||||||
},
|
|
||||||
...props,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {{
|
|
||||||
* name: string,
|
|
||||||
* icon: Gtk.Widget,
|
|
||||||
* label: Gtk.Widget,
|
|
||||||
* activate: () => void
|
|
||||||
* deactivate: () => void
|
|
||||||
* activateOnArrow?: boolean
|
|
||||||
* connection: [GObject.Object, () => boolean]
|
|
||||||
* } & import("../types/widgets/box").BoxProps} ArrowToggleButtonProps
|
|
||||||
* @param {ArrowToggleButtonProps} props
|
|
||||||
*/
|
|
||||||
export const ArrowToggleButton = ({
|
|
||||||
name,
|
|
||||||
icon,
|
|
||||||
label,
|
|
||||||
activate,
|
|
||||||
deactivate,
|
|
||||||
activateOnArrow = true,
|
|
||||||
connection: [service, condition],
|
|
||||||
}) =>
|
|
||||||
Widget.Box({
|
|
||||||
className: "qs-button surface",
|
|
||||||
setup: (self) =>
|
|
||||||
self.hook(service, () => {
|
|
||||||
self.toggleClassName("accent", condition());
|
|
||||||
}),
|
|
||||||
children: [
|
|
||||||
Widget.Button({
|
|
||||||
child: Widget.Box({
|
|
||||||
hexpand: true,
|
|
||||||
children: [icon, label],
|
|
||||||
}),
|
|
||||||
onClicked: () => {
|
|
||||||
if (condition()) {
|
|
||||||
deactivate();
|
|
||||||
if (opened.value === name) opened.value = "";
|
|
||||||
} else {
|
|
||||||
activate();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
Arrow({ name, activate: activateOnArrow && activate }),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {{
|
|
||||||
* icon: Gtk.Widget,
|
|
||||||
* label: Gtk.Widget,
|
|
||||||
* activate: () => void
|
|
||||||
* deactivate: () => void
|
|
||||||
* connection: [GObject.Object, () => boolean]
|
|
||||||
* } & import("../types/widgets/box").BoxProps} SimpleToggleButtonProps
|
|
||||||
* @param {SimpleToggleButtonProps} props
|
|
||||||
*/
|
|
||||||
export const SimpleToggleButton = ({
|
|
||||||
icon,
|
|
||||||
label,
|
|
||||||
activate,
|
|
||||||
deactivate,
|
|
||||||
connection: [service, condition],
|
|
||||||
}) =>
|
|
||||||
Widget.Box({
|
|
||||||
className: "qs-button surface",
|
|
||||||
setup: (self) =>
|
|
||||||
self.hook(service, () => {
|
|
||||||
self.toggleClassName("accent", condition());
|
|
||||||
}),
|
|
||||||
children: [
|
|
||||||
Widget.Button({
|
|
||||||
child: Widget.Box({
|
|
||||||
hexpand: true,
|
|
||||||
children: [icon, label],
|
|
||||||
}),
|
|
||||||
onClicked: () => {
|
|
||||||
if (condition()) {
|
|
||||||
deactivate();
|
|
||||||
} else {
|
|
||||||
activate();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {{
|
|
||||||
* name: string,
|
|
||||||
* icon: Gtk.Widget,
|
|
||||||
* title: string,
|
|
||||||
* content: Gtk.Widget[],
|
|
||||||
* } & import("../types/widgets/revealer").RevealerProps} MenuProps
|
|
||||||
* @param {MenuProps} props
|
|
||||||
*/
|
|
||||||
export const Menu = ({ name, icon, title, content, ...props }) =>
|
|
||||||
Widget.Revealer({
|
|
||||||
transition: "slide_down",
|
|
||||||
reveal_child: opened.bind().as((v) => v === name),
|
|
||||||
child: Widget.Box({
|
|
||||||
className: "qs-submenu surface",
|
|
||||||
vertical: true,
|
|
||||||
children: [
|
|
||||||
Widget.Box({
|
|
||||||
className: "qs-sub-title accent",
|
|
||||||
children: [
|
|
||||||
icon,
|
|
||||||
Widget.Label({
|
|
||||||
className: "bold f16",
|
|
||||||
truncate: "end",
|
|
||||||
label: title,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
Widget.Box({
|
|
||||||
vertical: true,
|
|
||||||
className: "qs-sub-content",
|
|
||||||
children: content,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
...props,
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @param {{type?: string, command?: string} & import("../types/widgets/button").ButtonProps} props */
|
|
||||||
export const SettingsButton = ({ type, command, ...props }) =>
|
|
||||||
Widget.Button({
|
|
||||||
onClicked: () => {
|
|
||||||
Utils.execAsync(command ?? `gnome-control-center ${type}`);
|
|
||||||
App.closeWindow("quicksettings");
|
|
||||||
},
|
|
||||||
hexpand: true,
|
|
||||||
child: Widget.Box({
|
|
||||||
children: [
|
|
||||||
Widget.Icon("emblem-system-symbolic"),
|
|
||||||
Widget.Label("Settings"),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
...props,
|
|
||||||
});
|
|
||||||
@@ -1,198 +0,0 @@
|
|||||||
// Stollen from https://github.com/Aylur/dotfiles/blob/main/ags/widget/PopupWindow.ts#
|
|
||||||
|
|
||||||
/** @typedef {import('../types/widgets/window').WindowProps} WindowProps */
|
|
||||||
/** @typedef {import('../types/widgets/revealer').RevealerProps} RevealerProps */
|
|
||||||
/** @typedef {import('../types/widgets/eventbox').EventBoxProps} EventBoxProps */
|
|
||||||
/** @typedef {import('gi://Gtk?version=3.0')} Gtk */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} name
|
|
||||||
* @param {EventBoxProps}
|
|
||||||
* @returns {any}
|
|
||||||
*/
|
|
||||||
export const Padding = (
|
|
||||||
name,
|
|
||||||
{ css = "", hexpand = true, vexpand = true } = {},
|
|
||||||
) =>
|
|
||||||
// Widget.Box({});
|
|
||||||
Widget.EventBox({
|
|
||||||
hexpand,
|
|
||||||
vexpand,
|
|
||||||
can_focus: false,
|
|
||||||
child: Widget.Box({ css }),
|
|
||||||
setup: (w) => w.on("button-press-event", () => App.toggleWindow(name)),
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} name
|
|
||||||
* @param {Child} child
|
|
||||||
* @param {Transition} [transition="slide_down"]
|
|
||||||
* @param {number} duration
|
|
||||||
*/
|
|
||||||
const PopupRevealer = (
|
|
||||||
name,
|
|
||||||
child,
|
|
||||||
transition = "slide_down",
|
|
||||||
duration = 500,
|
|
||||||
) =>
|
|
||||||
Widget.Box(
|
|
||||||
{ css: "padding: 1px;" },
|
|
||||||
Widget.Revealer({
|
|
||||||
transition,
|
|
||||||
child: Widget.Box({
|
|
||||||
class_name: "window-content",
|
|
||||||
child,
|
|
||||||
}),
|
|
||||||
transitionDuration: duration,
|
|
||||||
setup: (self) =>
|
|
||||||
self.hook(App, (_, wname, visible) => {
|
|
||||||
if (wname === name) self.reveal_child = visible;
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
/**
|
|
||||||
* @param {string} name
|
|
||||||
* @param {Child} child
|
|
||||||
* @param {Transition} [transition]
|
|
||||||
* @param {number} [duration]
|
|
||||||
* @returns {{ center: () => any; top: () => any; "top-right": () => any; "top-center": () => any; "top-left": () => any; "bottom-left": () => any; "bottom-center": () => any; "bottom-right": () => any; }}
|
|
||||||
*/
|
|
||||||
const Layout = (name, child, transition, duration) => ({
|
|
||||||
center: () =>
|
|
||||||
Widget.CenterBox(
|
|
||||||
{},
|
|
||||||
Padding(name),
|
|
||||||
Widget.CenterBox(
|
|
||||||
{ vertical: true },
|
|
||||||
Padding(name),
|
|
||||||
PopupRevealer(name, child, transition, duration),
|
|
||||||
Padding(name),
|
|
||||||
),
|
|
||||||
Padding(name),
|
|
||||||
),
|
|
||||||
top: () =>
|
|
||||||
Widget.CenterBox(
|
|
||||||
{},
|
|
||||||
Padding(name),
|
|
||||||
Widget.Box(
|
|
||||||
{ vertical: true },
|
|
||||||
PopupRevealer(name, child, transition, duration),
|
|
||||||
Padding(name),
|
|
||||||
),
|
|
||||||
Padding(name),
|
|
||||||
),
|
|
||||||
"top-right": () =>
|
|
||||||
Widget.Box(
|
|
||||||
{},
|
|
||||||
Padding(name),
|
|
||||||
Widget.Box(
|
|
||||||
{
|
|
||||||
hexpand: false,
|
|
||||||
vertical: true,
|
|
||||||
},
|
|
||||||
PopupRevealer(name, child, transition, duration),
|
|
||||||
Padding(name),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
"top-center": () =>
|
|
||||||
Widget.Box(
|
|
||||||
{},
|
|
||||||
Padding(name),
|
|
||||||
Widget.Box(
|
|
||||||
{
|
|
||||||
hexpand: false,
|
|
||||||
vertical: true,
|
|
||||||
},
|
|
||||||
PopupRevealer(name, child, transition, duration),
|
|
||||||
Padding(name),
|
|
||||||
),
|
|
||||||
Padding(name),
|
|
||||||
),
|
|
||||||
"top-left": () =>
|
|
||||||
Widget.Box(
|
|
||||||
{},
|
|
||||||
Widget.Box(
|
|
||||||
{
|
|
||||||
hexpand: false,
|
|
||||||
vertical: true,
|
|
||||||
},
|
|
||||||
PopupRevealer(name, child, transition, duration),
|
|
||||||
Padding(name),
|
|
||||||
),
|
|
||||||
Padding(name),
|
|
||||||
),
|
|
||||||
"bottom-left": () =>
|
|
||||||
Widget.Box(
|
|
||||||
{},
|
|
||||||
Widget.Box(
|
|
||||||
{
|
|
||||||
hexpand: false,
|
|
||||||
vertical: true,
|
|
||||||
},
|
|
||||||
Padding(name),
|
|
||||||
PopupRevealer(name, child, transition, duration),
|
|
||||||
),
|
|
||||||
Padding(name),
|
|
||||||
),
|
|
||||||
"bottom-center": () =>
|
|
||||||
Widget.Box(
|
|
||||||
{},
|
|
||||||
Padding(name),
|
|
||||||
Widget.Box(
|
|
||||||
{
|
|
||||||
hexpand: false,
|
|
||||||
vertical: true,
|
|
||||||
},
|
|
||||||
Padding(name),
|
|
||||||
PopupRevealer(name, child, transition, duration),
|
|
||||||
),
|
|
||||||
Padding(name),
|
|
||||||
),
|
|
||||||
"bottom-right": () =>
|
|
||||||
Widget.Box(
|
|
||||||
{},
|
|
||||||
Padding(name),
|
|
||||||
Widget.Box(
|
|
||||||
{
|
|
||||||
hexpand: false,
|
|
||||||
vertical: true,
|
|
||||||
},
|
|
||||||
Padding(name),
|
|
||||||
PopupRevealer(name, child, transition, duration),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @typedef {RevealerProps["transition"]} Transition */
|
|
||||||
/** @typedef {WindowProps["child"]} Child */
|
|
||||||
/**
|
|
||||||
* @typedef {Omit<WindowProps, "name"> & {
|
|
||||||
* name: string
|
|
||||||
* layout?: keyof ReturnType<typeof Layout>
|
|
||||||
* transition?: Transition,
|
|
||||||
* duration?: number
|
|
||||||
* }} PopupWindowProps
|
|
||||||
* @param {PopupWindowProps} props
|
|
||||||
*/
|
|
||||||
export default ({
|
|
||||||
name,
|
|
||||||
child,
|
|
||||||
layout = "center",
|
|
||||||
transition,
|
|
||||||
exclusivity = "ignore",
|
|
||||||
duration,
|
|
||||||
...props
|
|
||||||
}) =>
|
|
||||||
Widget.Window({
|
|
||||||
name,
|
|
||||||
class_names: [name, "popup-window"],
|
|
||||||
setup: (w) => w.keybind("Escape", () => App.closeWindow(name)),
|
|
||||||
visible: false,
|
|
||||||
keymode: "on-demand",
|
|
||||||
exclusivity,
|
|
||||||
layer: "top",
|
|
||||||
// anchor: ["top", "right"],
|
|
||||||
anchor: ["top", "bottom", "right", "left"],
|
|
||||||
child: Layout(name, child, transition, duration)[layout](),
|
|
||||||
...props,
|
|
||||||
});
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import GLib from "gi://GLib?version=2.0";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string | null | undefined} name
|
|
||||||
* @param {string | null | undefined} [fallback]
|
|
||||||
*/
|
|
||||||
export function icon(name, fallback) {
|
|
||||||
if (!name) return fallback || "";
|
|
||||||
if (typeof name !== "string") return name;
|
|
||||||
|
|
||||||
const sub = substitutes[name];
|
|
||||||
if (sub && Utils.lookUpIcon(sub)) return sub;
|
|
||||||
if (Utils.lookUpIcon(name)) return name;
|
|
||||||
|
|
||||||
if (GLib.file_test(name, GLib.FileTest.EXISTS)) return name;
|
|
||||||
|
|
||||||
return fallback || "";
|
|
||||||
}
|
|
||||||
|
|
||||||
export const substitutes = {
|
|
||||||
"transmission-gtk": "transmission",
|
|
||||||
"blueberry.py": "blueberry",
|
|
||||||
Caprine: "facebook-messenger",
|
|
||||||
"com.raggesilver.BlackBox-symbolic": "terminal-symbolic",
|
|
||||||
"org.wezfurlong.wezterm-symbolic": "terminal-symbolic",
|
|
||||||
"audio-headset-bluetooth": "audio-headphones-symbolic",
|
|
||||||
"audio-card-analog-usb": "audio-speakers-symbolic",
|
|
||||||
"audio-card-analog-pci": "audio-card-symbolic",
|
|
||||||
"preferences-system": "emblem-system-symbolic",
|
|
||||||
"com.github.Aylur.ags-symbolic": "controls-symbolic",
|
|
||||||
"com.github.Aylur.ags": "controls-symbolic",
|
|
||||||
};
|
|
||||||
@@ -1,228 +0,0 @@
|
|||||||
import { icon } from "../misc/utils.js";
|
|
||||||
import {
|
|
||||||
Arrow,
|
|
||||||
Menu,
|
|
||||||
SettingsButton,
|
|
||||||
SimpleToggleButton,
|
|
||||||
} from "../misc/menu.js";
|
|
||||||
|
|
||||||
const audio = await Service.import("audio");
|
|
||||||
|
|
||||||
const volumeIcons = /** @type {const} */ ([
|
|
||||||
[101, "overamplified"],
|
|
||||||
[67, "high"],
|
|
||||||
[34, "medium"],
|
|
||||||
[1, "low"],
|
|
||||||
[0, "muted"],
|
|
||||||
]);
|
|
||||||
|
|
||||||
/** @param {number} volume */
|
|
||||||
export const getIcon = (volume) => {
|
|
||||||
const icon = volumeIcons.find(([threshold]) => threshold <= volume)?.[1];
|
|
||||||
return `audio-volume-${icon}-symbolic`;
|
|
||||||
};
|
|
||||||
|
|
||||||
/** @param {{type?: "speaker" | "microphone"} & import("../types/widgets/icon.js").IconProps} props */
|
|
||||||
export const VolumeIndicator = ({ type = "speaker", ...props }) =>
|
|
||||||
Widget.Icon(props).hook(audio, (self) => {
|
|
||||||
if (audio[type].is_muted) {
|
|
||||||
self.icon = "audio-volume-muted-symbolic";
|
|
||||||
self.tooltip_text = "Muted";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const vol = audio[type].volume * 100;
|
|
||||||
self.icon = getIcon(vol);
|
|
||||||
self.tooltip_text = `Volume: ${Math.floor(vol)}%`;
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @param {import("../types/widgets/icon.js").IconProps} props */
|
|
||||||
export const MicrophoneIndicator = (props) =>
|
|
||||||
Widget.Icon(props).hook(audio, (self) => {
|
|
||||||
self.visible = audio.microphone.is_muted || audio.recorders.length > 0;
|
|
||||||
if (audio.microphone.is_muted) self.icon = "microphone-disabled-symbolic";
|
|
||||||
else if (audio.recorders.length > 0)
|
|
||||||
self.icon = "microphone-sensitivity-high-symbolic";
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @param {{type?: "speaker" | "microphone"} & import("../types/widgets/slider.js").SliderProps} props */
|
|
||||||
const VolumeSlider = ({ type = "speaker", ...props }) =>
|
|
||||||
Widget.Slider({
|
|
||||||
hexpand: true,
|
|
||||||
drawValue: false,
|
|
||||||
onChange: ({ value, dragging }) => {
|
|
||||||
if (dragging) {
|
|
||||||
audio[type].volume = value;
|
|
||||||
audio[type].is_muted = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
value: audio[type].bind("volume"),
|
|
||||||
css: audio[type].bind("is_muted").as((x) => `opacity: ${x ? 0.7 : 1}`),
|
|
||||||
...props,
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @param {{type?: "speaker" | "microphone"} & import("../types/widgets/box.js").BoxProps} props */
|
|
||||||
export const Volume = ({ type = "speaker", ...props }) =>
|
|
||||||
Widget.Box({
|
|
||||||
className: "qs-slider",
|
|
||||||
children: [
|
|
||||||
Widget.Button({
|
|
||||||
onClicked: () => {
|
|
||||||
audio[type].is_muted = !audio[type].is_muted;
|
|
||||||
},
|
|
||||||
child: VolumeIndicator({ type }),
|
|
||||||
}),
|
|
||||||
VolumeSlider({ type }),
|
|
||||||
Widget.Label({
|
|
||||||
label: audio[type].bind("volume").as(
|
|
||||||
(vol) =>
|
|
||||||
`${Math.floor(vol * 100)
|
|
||||||
.toString()
|
|
||||||
.padStart(3)}%`,
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
Widget.Box({
|
|
||||||
vpack: "center",
|
|
||||||
child: Arrow({ name: "sink-selector" }),
|
|
||||||
}),
|
|
||||||
Widget.Box({
|
|
||||||
vpack: "center",
|
|
||||||
child: Arrow({ name: "app-mixer" }),
|
|
||||||
visible: audio.bind("apps").as((a) => a.length > 0),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
...props,
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @param {Partial<import("../misc/menu.js").SimpleToggleButtonProps>} props */
|
|
||||||
export const MuteToggle = ({ ...props } = {}) =>
|
|
||||||
SimpleToggleButton({
|
|
||||||
icon: Widget.Icon({
|
|
||||||
className: "qs-icon",
|
|
||||||
icon: audio.microphone
|
|
||||||
.bind("is_muted")
|
|
||||||
.as((x) =>
|
|
||||||
x
|
|
||||||
? "microphone-disabled-symbolic"
|
|
||||||
: "microphone-sensitivity-high-symbolic",
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
label: Widget.Label({
|
|
||||||
label: audio.microphone
|
|
||||||
.bind("is_muted")
|
|
||||||
.as((x) => (x ? "Unmute" : "Mute")),
|
|
||||||
}),
|
|
||||||
activate: () => {
|
|
||||||
audio.microphone.is_muted = true;
|
|
||||||
},
|
|
||||||
deactivate: () => {
|
|
||||||
audio.microphone.is_muted = false;
|
|
||||||
},
|
|
||||||
connection: [audio.microphone, () => audio.microphone.is_muted || false],
|
|
||||||
...props,
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @param {Partial<import("../misc/menu.js").MenuProps>} props */
|
|
||||||
export const SinkSelector = (props) =>
|
|
||||||
Menu({
|
|
||||||
name: "sink-selector",
|
|
||||||
icon: Widget.Icon("audio-headphones-symbolic"),
|
|
||||||
title: "Sink Selector",
|
|
||||||
content: [
|
|
||||||
Widget.Box({
|
|
||||||
className: "qs-sub-sub-content",
|
|
||||||
vertical: true,
|
|
||||||
children: audio.bind("speakers").as((a) => a.map(SinkItem)),
|
|
||||||
}),
|
|
||||||
Widget.Separator({ className: "accent" }),
|
|
||||||
SettingsButton({ type: "sound" }),
|
|
||||||
],
|
|
||||||
...props,
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @param {import("../types/service/audio.js").Stream} stream */
|
|
||||||
const SinkItem = (stream) =>
|
|
||||||
Widget.Button({
|
|
||||||
hexpand: true,
|
|
||||||
onClicked: () => {
|
|
||||||
audio.speaker = stream;
|
|
||||||
},
|
|
||||||
child: Widget.Box({
|
|
||||||
children: [
|
|
||||||
Widget.Icon({
|
|
||||||
icon: icon(stream.icon_name, "audio-x-generic-symbolic"),
|
|
||||||
tooltip_text: stream.icon_name || "",
|
|
||||||
}),
|
|
||||||
Widget.Label({
|
|
||||||
label: (stream.description || "").split(" ").slice(0, 4).join(" "),
|
|
||||||
}),
|
|
||||||
Widget.Icon({
|
|
||||||
icon: "object-select-symbolic",
|
|
||||||
hexpand: true,
|
|
||||||
hpack: "end",
|
|
||||||
visible: audio.speaker.bind("stream").as((s) => s === stream.stream),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @param {Partial<import("../misc/menu.js").MenuProps>} props */
|
|
||||||
export const AppMixer = (props) =>
|
|
||||||
Menu({
|
|
||||||
name: "app-mixer",
|
|
||||||
icon: Widget.Icon("audio-volume-high-symbolic"),
|
|
||||||
title: "App Mixer",
|
|
||||||
content: [
|
|
||||||
Widget.Box({
|
|
||||||
vertical: true,
|
|
||||||
className: "qs-sub-sub-content",
|
|
||||||
children: audio.bind("apps").as((a) => a.map(MixerItem)),
|
|
||||||
}),
|
|
||||||
Widget.Separator({ className: "accent" }),
|
|
||||||
SettingsButton({ type: "sound" }),
|
|
||||||
],
|
|
||||||
...props,
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @param {import("../types/service/audio.js").Stream} stream */
|
|
||||||
const MixerItem = (stream) =>
|
|
||||||
Widget.Box({
|
|
||||||
hexpand: true,
|
|
||||||
children: [
|
|
||||||
Widget.Icon({
|
|
||||||
tooltipText: stream.bind("name").as((n) => n || ""),
|
|
||||||
icon: stream
|
|
||||||
.bind("name")
|
|
||||||
.as((n) =>
|
|
||||||
n && Utils.lookUpIcon(n) ? n : "audio-x-generic-symbolic",
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
Widget.Box({
|
|
||||||
vertical: true,
|
|
||||||
children: [
|
|
||||||
Widget.Label({
|
|
||||||
xalign: 0,
|
|
||||||
truncate: "end",
|
|
||||||
max_width_chars: 28,
|
|
||||||
label: stream.bind("description").as((d) => d || ""),
|
|
||||||
}),
|
|
||||||
Widget.Slider({
|
|
||||||
hexpand: true,
|
|
||||||
draw_value: false,
|
|
||||||
value: stream.bind("volume"),
|
|
||||||
onChange: ({ value }) => {
|
|
||||||
stream.volume = value;
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
Widget.Label({
|
|
||||||
css: "padding: 12px",
|
|
||||||
label: stream.bind("volume").as(
|
|
||||||
(x) =>
|
|
||||||
`${Math.floor(x * 100)
|
|
||||||
.toString()
|
|
||||||
.padStart(3)}%`,
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
const battery = await Service.import("battery");
|
|
||||||
|
|
||||||
/** @param {import("../types/widgets/box").BoxProps} props */
|
|
||||||
export const Indicator = ({ ...props }) =>
|
|
||||||
Widget.Box({
|
|
||||||
children: [
|
|
||||||
Widget.Icon({
|
|
||||||
icon: battery.bind("icon_name"),
|
|
||||||
className: Utils.merge(
|
|
||||||
[
|
|
||||||
battery.bind("charging"),
|
|
||||||
battery.bind("charged"),
|
|
||||||
battery.bind("percent"),
|
|
||||||
],
|
|
||||||
(charging, charged, percent) => {
|
|
||||||
if (charging || charged) return "green";
|
|
||||||
if (percent < 30) return "red";
|
|
||||||
return "";
|
|
||||||
},
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
Widget.Label({
|
|
||||||
label: battery.bind("percent").as((x) => `${x}%`),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
visible: battery.bind("available"),
|
|
||||||
...props,
|
|
||||||
});
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
import { ArrowToggleButton, Menu, SettingsButton } from "../misc/menu.js";
|
|
||||||
|
|
||||||
const bluetooth = await Service.import("bluetooth");
|
|
||||||
|
|
||||||
const connected = Utils.merge(
|
|
||||||
[bluetooth.bind("enabled"), bluetooth.bind("connected_devices")],
|
|
||||||
(enabled, devices) => enabled && devices.length > 0,
|
|
||||||
);
|
|
||||||
|
|
||||||
/** @param {{hideIfDisabled?: boolean} & import("../types/widgets/icon.js").IconProps} props */
|
|
||||||
export const Indicator = ({ hideIfDisabled = false, ...props } = {}) =>
|
|
||||||
Widget.Icon({
|
|
||||||
icon: connected.as(
|
|
||||||
(x) => `bluetooth-${x ? "active" : "disabled"}-symbolic`,
|
|
||||||
),
|
|
||||||
visible: connected.as((x) => x || !hideIfDisabled),
|
|
||||||
...props,
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @param {import("../types/widgets/label.js").LabelProps} props */
|
|
||||||
export const ConnectedLabel = (props) =>
|
|
||||||
Widget.Label(props).hook(bluetooth, (self) => {
|
|
||||||
if (!bluetooth.enabled) self.label = "Disabled";
|
|
||||||
|
|
||||||
if (bluetooth.connected_devices.length === 0) self.label = "Disconnected";
|
|
||||||
else if (bluetooth.connected_devices.length === 1)
|
|
||||||
self.label = bluetooth.connected_devices[0].alias;
|
|
||||||
else self.label = `${bluetooth.connected_devices.length} Connected`;
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @param {Partial<import("../misc/menu.js").ArrowToggleButtonProps>} props */
|
|
||||||
export const Toggle = (props) =>
|
|
||||||
ArrowToggleButton({
|
|
||||||
name: "bluetooth",
|
|
||||||
icon: Indicator({ className: "qs-icon" }),
|
|
||||||
label: ConnectedLabel({
|
|
||||||
max_width_chars: 20,
|
|
||||||
}),
|
|
||||||
activate: () => {
|
|
||||||
bluetooth.enabled = true;
|
|
||||||
},
|
|
||||||
deactivate: () => {
|
|
||||||
bluetooth.enabled = false;
|
|
||||||
},
|
|
||||||
connection: [bluetooth, () => bluetooth.enabled],
|
|
||||||
...props,
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @param {Partial<import("../misc/menu.js").MenuProps>} props */
|
|
||||||
export const Selection = (props) =>
|
|
||||||
Menu({
|
|
||||||
name: "bluetooth",
|
|
||||||
icon: Indicator({}),
|
|
||||||
title: "Bluetooth",
|
|
||||||
content: [
|
|
||||||
Widget.Box({
|
|
||||||
vertical: true,
|
|
||||||
className: "qs-sub-sub-content",
|
|
||||||
children: bluetooth.bind("devices").as((x) => x.map(DeviceItem)),
|
|
||||||
}),
|
|
||||||
Widget.Separator({ className: "accent" }),
|
|
||||||
SettingsButton({ command: "blueberry" }),
|
|
||||||
],
|
|
||||||
...props,
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @param {import("../types/service/bluetooth.js").BluetoothDevice} device */
|
|
||||||
const DeviceItem = (device) =>
|
|
||||||
Widget.Box({
|
|
||||||
children: [
|
|
||||||
Widget.Icon(`${device.icon_name}-symbolic`),
|
|
||||||
Widget.Label(device.name),
|
|
||||||
Widget.Box({ hexpand: true }),
|
|
||||||
Widget.Label({
|
|
||||||
label: `${device.battery_percentage}%`,
|
|
||||||
css: "padding-right: 24px;",
|
|
||||||
visible: device.bind("battery_percentage").as((x) => x > 0),
|
|
||||||
}),
|
|
||||||
Widget.Spinner({
|
|
||||||
active: device.bind("connecting"),
|
|
||||||
visible: device.bind("connecting"),
|
|
||||||
}),
|
|
||||||
Widget.Switch({
|
|
||||||
active: device.bind("connected"),
|
|
||||||
visible: device.bind("connecting").as((p) => !p),
|
|
||||||
setup: (self) =>
|
|
||||||
// TODO: If connecting to the device failed, reset back the switch to `active: false`.
|
|
||||||
self.connect("state_set", () => {
|
|
||||||
device.setConnection(self.active);
|
|
||||||
return true;
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
import brightness from "../services/brightness.js";
|
|
||||||
|
|
||||||
/** @param {import("../types/widgets/slider.js").SliderProps} props */
|
|
||||||
const BrightnessSlider = (props) =>
|
|
||||||
Widget.Slider({
|
|
||||||
drawValue: false,
|
|
||||||
hexpand: true,
|
|
||||||
value: brightness.bind("screen"),
|
|
||||||
onChange: ({ value }) => {
|
|
||||||
brightness.screen = value;
|
|
||||||
},
|
|
||||||
...props,
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @param {import("../types/widgets/box.js").BoxProps} props */
|
|
||||||
export const Brightness = (props) =>
|
|
||||||
Widget.Box({
|
|
||||||
className: "qs-slider",
|
|
||||||
children: [
|
|
||||||
Widget.Icon({
|
|
||||||
vpack: "center",
|
|
||||||
icon: "display-brightness-symbolic",
|
|
||||||
tooltipText: brightness
|
|
||||||
.bind("screen")
|
|
||||||
.as((x) => `Screen Brightness: ${Math.floor(x * 100)}%`),
|
|
||||||
}),
|
|
||||||
BrightnessSlider({}),
|
|
||||||
Widget.Label({
|
|
||||||
label: brightness.bind("screen").as(
|
|
||||||
(x) =>
|
|
||||||
`${Math.floor(x * 100)
|
|
||||||
.toString()
|
|
||||||
.padStart(3)}%`,
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
...props,
|
|
||||||
});
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import GLib from "gi://GLib";
|
|
||||||
|
|
||||||
export const clock = Variable(GLib.DateTime.new_now_local(), {
|
|
||||||
poll: [1000, () => GLib.DateTime.new_now_local()],
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {{format?: string} & import("../types/widgets/label").LabelProps} props
|
|
||||||
*/
|
|
||||||
export const Clock = ({ format = "%a %d %b %H:%M ", ...props } = {}) =>
|
|
||||||
Widget.Label({
|
|
||||||
...props,
|
|
||||||
label: Utils.derive([clock], (c) => c.format(format) || "").bind(),
|
|
||||||
});
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
import Gio from "gi://Gio";
|
|
||||||
import { SimpleToggleButton } from "../misc/menu.js";
|
|
||||||
|
|
||||||
const interfaceXml = `
|
|
||||||
<node name="/nl/whynothugo/darkman">
|
|
||||||
<interface name="nl.whynothugo.darkman">
|
|
||||||
<signal name="ModeChanged">
|
|
||||||
<arg name="NewMode" type="s" />
|
|
||||||
</signal>
|
|
||||||
<property name="Mode" type="s" access="readwrite">
|
|
||||||
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true" />
|
|
||||||
</property>
|
|
||||||
</interface>
|
|
||||||
</node>
|
|
||||||
`;
|
|
||||||
const Darkman = Gio.DBusProxy.makeProxyWrapper(interfaceXml);
|
|
||||||
|
|
||||||
const theme = Variable(/** @type {"light" | "dark"} */ ("light"));
|
|
||||||
|
|
||||||
/** @param {Partial<import("../misc/menu.js").SimpleToggleButtonProps>} props */
|
|
||||||
export const Toggle = ({ ...props } = {}) =>
|
|
||||||
SimpleToggleButton({
|
|
||||||
icon: Widget.Icon({
|
|
||||||
className: "qs-icon",
|
|
||||||
icon: theme
|
|
||||||
.bind()
|
|
||||||
.as((x) =>
|
|
||||||
x === "light"
|
|
||||||
? "weather-clear-symbolic"
|
|
||||||
: "weather-clear-night-symbolic",
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
label: Widget.Label({
|
|
||||||
label: theme.bind().as((x) => (x === "light" ? "Light" : "Dark")),
|
|
||||||
}),
|
|
||||||
activate: () => theme.setValue("dark"),
|
|
||||||
deactivate: () => theme.setValue("light"),
|
|
||||||
connection: [theme, () => theme.value === "dark"],
|
|
||||||
...props,
|
|
||||||
});
|
|
||||||
|
|
||||||
function init() {
|
|
||||||
const darkman = Darkman(
|
|
||||||
Gio.DBus.session,
|
|
||||||
"nl.whynothugo.darkman",
|
|
||||||
"/nl/whynothugo/darkman",
|
|
||||||
);
|
|
||||||
|
|
||||||
theme.value = darkman.Mode;
|
|
||||||
theme.connect("changed", () => {
|
|
||||||
darkman.Mode = theme.value;
|
|
||||||
});
|
|
||||||
darkman.connectSignal("ModeChanged", (_proxy, _senderName, nTheme) => {
|
|
||||||
theme.value = nTheme[0];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
init();
|
|
||||||
@@ -1,290 +0,0 @@
|
|||||||
import { AnimatedCircProg } from "../misc/circular-progress.js";
|
|
||||||
|
|
||||||
const mpris = await Service.import("mpris");
|
|
||||||
|
|
||||||
/** @param {{player: import("types/service/mpris").MprisPlayer} & import("../types/widgets/icon").IconProps} props */
|
|
||||||
const PlayerIcon = ({ player, ...props }) =>
|
|
||||||
Widget.Icon({
|
|
||||||
size: 24,
|
|
||||||
hpack: "start",
|
|
||||||
vpack: "start",
|
|
||||||
tooltipText: player.identity || "",
|
|
||||||
icon: player.bind("entry").transform((entry) => {
|
|
||||||
const name = `${entry}-symbolic`;
|
|
||||||
return Utils.lookUpIcon(name)
|
|
||||||
? name
|
|
||||||
: Utils.lookUpIcon(entry)
|
|
||||||
? entry
|
|
||||||
: "audio-x-generic-symbolic";
|
|
||||||
}),
|
|
||||||
...props,
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @param {{player: import("types/service/mpris").MprisPlayer} & import("../types/widgets/label").LabelProps} props */
|
|
||||||
const TitleLabel = ({ player, ...props }) =>
|
|
||||||
Widget.Label({
|
|
||||||
wrap: true,
|
|
||||||
truncate: "end",
|
|
||||||
hpack: "start",
|
|
||||||
label: player.bind("track_title"),
|
|
||||||
...props,
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @param {{player: import("types/service/mpris").MprisPlayer} & import("../types/widgets/label").LabelProps} props */
|
|
||||||
const ArtistLabel = ({ player, ...props }) =>
|
|
||||||
Widget.Label({
|
|
||||||
wrap: true,
|
|
||||||
truncate: "end",
|
|
||||||
hpack: "start",
|
|
||||||
label: player.bind("track_artists").transform((x) => x.map(y => y.replace(/- Topic$/, "")).join(", ")),
|
|
||||||
...props,
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @param {{
|
|
||||||
* player: import("types/service/mpris").MprisPlayer,
|
|
||||||
* iconProps?: import("types/widgets/icon").IconProps,
|
|
||||||
* } & import("types/widgets/button").ButtonProps} props */
|
|
||||||
export const PlayPause = ({ player, iconProps = {}, ...props }) =>
|
|
||||||
Widget.Button({
|
|
||||||
child: Widget.Icon({
|
|
||||||
icon: player.bind("play_back_status").as(
|
|
||||||
(x) =>
|
|
||||||
({
|
|
||||||
Playing: "media-playback-pause-symbolic",
|
|
||||||
Paused: "media-playback-start-symbolic",
|
|
||||||
Stopped: "media-playback-start-symbolic",
|
|
||||||
})[x],
|
|
||||||
),
|
|
||||||
...iconProps,
|
|
||||||
}),
|
|
||||||
onClicked: () => player.playPause(),
|
|
||||||
visible: player.bind("can_play"),
|
|
||||||
...props,
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @param {{player: import("types/service/mpris").MprisPlayer} & import("../types/widgets/button").ButtonProps} props */
|
|
||||||
const PreviousButton = ({ player, ...props }) =>
|
|
||||||
Widget.Button({
|
|
||||||
child: Widget.Icon({ icon: "media-skip-backward-symbolic" }),
|
|
||||||
onClicked: () => player.previous(),
|
|
||||||
visible: player.bind("can_go_prev"),
|
|
||||||
...props,
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @param {{player: import("types/service/mpris").MprisPlayer} & import("../types/widgets/button").ButtonProps} props */
|
|
||||||
const NextButton = ({ player, ...props }) =>
|
|
||||||
Widget.Button({
|
|
||||||
child: Widget.Icon({ icon: "media-skip-forward-symbolic" }),
|
|
||||||
onClicked: () => player.next(),
|
|
||||||
visible: player.bind("can_go_next"),
|
|
||||||
...props,
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @param {{player: import("types/service/mpris").MprisPlayer} & import("../types/widgets/slider").SliderProps} props */
|
|
||||||
const PositionSlider = ({ player, ...props }) =>
|
|
||||||
Widget.Slider({
|
|
||||||
className: "mpris-position-slider",
|
|
||||||
drawValue: false,
|
|
||||||
onChange: ({ value }) => {
|
|
||||||
player.position = value * player.length;
|
|
||||||
},
|
|
||||||
visible: player.bind("length").as((l) => l > 0),
|
|
||||||
setup: (self) => {
|
|
||||||
function update() {
|
|
||||||
const value = player.position / player.length;
|
|
||||||
self.value = value > 0 ? value : 0;
|
|
||||||
}
|
|
||||||
self.hook(player, update);
|
|
||||||
self.hook(player, update, "position");
|
|
||||||
self.poll(1000, update);
|
|
||||||
},
|
|
||||||
...props,
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @param {{player: import("types/service/mpris").MprisPlayer} & import("../types/widgets/box").BoxProps} props */
|
|
||||||
const PositionCircle = ({ player, child, ...props }) =>
|
|
||||||
Widget.Box({
|
|
||||||
child: Widget.Overlay({
|
|
||||||
child: AnimatedCircProg({
|
|
||||||
css: `
|
|
||||||
min-width: 0.136rem;
|
|
||||||
min-height: 1.636rem;
|
|
||||||
padding: 0rem;
|
|
||||||
`,
|
|
||||||
vpack: "center",
|
|
||||||
hpack: "center",
|
|
||||||
className: "accent-rev",
|
|
||||||
extraSetup: (self) => {
|
|
||||||
function update() {
|
|
||||||
const value = player.position / player.length;
|
|
||||||
self.css = `
|
|
||||||
font-size: ${Math.max(value * 100, 0)}px;
|
|
||||||
min-width: 0.136rem;
|
|
||||||
min-height: 1.636rem;
|
|
||||||
padding: 0rem;
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
self.hook(player, update);
|
|
||||||
self.hook(player, update, "position");
|
|
||||||
self.poll(1000, update);
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
overlays: [child],
|
|
||||||
}),
|
|
||||||
...props,
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @param {{player: import("types/service/mpris").MprisPlayer} & import("../types/widgets/box").BoxProps} props */
|
|
||||||
export const LinePlayer = ({ player, ...props }) =>
|
|
||||||
Widget.Box({
|
|
||||||
visible: player.bind("play_back_status").as((x) => x !== "Stopped"),
|
|
||||||
children: [
|
|
||||||
PositionCircle({
|
|
||||||
player,
|
|
||||||
child: PlayPause({ player, iconProps: { size: 10 } }),
|
|
||||||
css: "margin-right: 12px;",
|
|
||||||
}),
|
|
||||||
Widget.Label({
|
|
||||||
label: Utils.merge(
|
|
||||||
[player.bind("track_title"), player.bind("track_artists")],
|
|
||||||
(title, artists) => `${title} - ${artists.map(x => x.replace(/- Topic$/, "")).join(", ")}`,
|
|
||||||
),
|
|
||||||
maxWidthChars: 30,
|
|
||||||
wrap: true,
|
|
||||||
truncate: "end",
|
|
||||||
hpack: "start",
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
...props,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const activePlayer = Variable(mpris.players[0]);
|
|
||||||
mpris.connect("player-added", (_, bus) => {
|
|
||||||
mpris.getPlayer(bus)?.connect("changed", (player) => {
|
|
||||||
if (player?.play_back_status !== "Stopped") {
|
|
||||||
activePlayer.value = player || mpris.players[0];
|
|
||||||
} else {
|
|
||||||
activePlayer.value = mpris.players[0];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
mpris.connect("player-closed", (_, bus) => {
|
|
||||||
if (activePlayer.value.bus_name === bus)
|
|
||||||
activePlayer.value = mpris.players[0];
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @param {{player?: import("types/service/mpris").MprisPlayer | null} & import("../types/widgets/box").BoxProps} props */
|
|
||||||
export const MprisPlayer = ({ player, ...props }) => {
|
|
||||||
if (!player) return Widget.Box({ visible: false });
|
|
||||||
const colors = getMaterialColors(player);
|
|
||||||
|
|
||||||
return Widget.Box({
|
|
||||||
visible: player.bind("play_back_status").as((x) => x !== "Stopped"),
|
|
||||||
className: `mpris-cover-art ${props.className}`,
|
|
||||||
css: Utils.merge(
|
|
||||||
[colors.bind(), player.bind("cover_path")],
|
|
||||||
(colors, cover) => `
|
|
||||||
background-image: radial-gradient(circle, rgba(0, 0, 0, 0.4) 30%, ${colors.primary}), url("${cover}"); \
|
|
||||||
color: ${colors.onBackground};
|
|
||||||
`,
|
|
||||||
),
|
|
||||||
vexpand: false,
|
|
||||||
children: [
|
|
||||||
Widget.CenterBox({
|
|
||||||
vertical: true,
|
|
||||||
hexpand: true,
|
|
||||||
startWidget: Widget.Box({
|
|
||||||
vertical: true,
|
|
||||||
vexpand: true,
|
|
||||||
css: colors.bind().as((x) => `color: ${x.onBackground}`),
|
|
||||||
child: PlayerIcon({ player }),
|
|
||||||
}),
|
|
||||||
centerWidget: Widget.Box({
|
|
||||||
hexpand: true,
|
|
||||||
children: [
|
|
||||||
Widget.Box({
|
|
||||||
css: colors.bind().as(
|
|
||||||
(x) => `
|
|
||||||
color: ${x.onBackground};
|
|
||||||
margin-right: 12px;
|
|
||||||
`,
|
|
||||||
),
|
|
||||||
vertical: true,
|
|
||||||
vpack: "center",
|
|
||||||
hexpand: true,
|
|
||||||
children: [
|
|
||||||
TitleLabel({
|
|
||||||
player,
|
|
||||||
css: "font-weight: 600; font-size: 19px;",
|
|
||||||
maxWidthChars: 33,
|
|
||||||
}),
|
|
||||||
ArtistLabel({
|
|
||||||
player,
|
|
||||||
css: "font-weight: 400; font-size: 17px;",
|
|
||||||
maxWidthChars: 40,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
Widget.Box({
|
|
||||||
children: [
|
|
||||||
PlayPause({
|
|
||||||
player,
|
|
||||||
hpack: "end",
|
|
||||||
className: "mpris-play",
|
|
||||||
css: colors.bind().as(
|
|
||||||
(x) => `
|
|
||||||
background-color: ${x.primary};
|
|
||||||
color: ${x.onPrimary};
|
|
||||||
`,
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
endWidget: Widget.Box({
|
|
||||||
css: colors.bind().as((x) => `color: ${x.onBackground}`),
|
|
||||||
vpack: "end",
|
|
||||||
children: [
|
|
||||||
Widget.Box({
|
|
||||||
hexpand: true,
|
|
||||||
children: [
|
|
||||||
PreviousButton({ player, css: "margin-left: 16px;" }),
|
|
||||||
PositionSlider({ player, hexpand: true }),
|
|
||||||
NextButton({ player, css: "margin-right: 16px;" }),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
...props,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: Move ret inside getMaterialColors to support multiple players.
|
|
||||||
const ret = Variable({
|
|
||||||
primary: "#222222",
|
|
||||||
onPrimary: "#ffffff",
|
|
||||||
background: "#222222",
|
|
||||||
onBackground: "#ffffff",
|
|
||||||
});
|
|
||||||
/** @param {import("types/service/mpris").MprisPlayer} player */
|
|
||||||
export const getMaterialColors = (player) => {
|
|
||||||
// TODO: Move that to a hook to allow graceful disconnections
|
|
||||||
player.connect("changed", (player) => {
|
|
||||||
const cover = player.cover_path;
|
|
||||||
// TODO: Wait for the cover to be downloaded, currently we hope that it's ready in <100ms
|
|
||||||
Utils.timeout(100, () => {
|
|
||||||
Utils.execAsync(["covercolors", cover])
|
|
||||||
.then((colors) => {
|
|
||||||
const col = JSON.parse(colors);
|
|
||||||
if (!col) return;
|
|
||||||
ret.setValue(col);
|
|
||||||
})
|
|
||||||
.catch(print);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
};
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
import { ArrowToggleButton, Menu, SettingsButton } from "../misc/menu.js";
|
|
||||||
|
|
||||||
const network = await Service.import("network");
|
|
||||||
|
|
||||||
/** @param {import("../types/widgets/icon.js").IconProps} props*/
|
|
||||||
export const Indicator = (props) =>
|
|
||||||
Widget.Icon(props).hook(network, (self) => {
|
|
||||||
self.icon =
|
|
||||||
network[network.primary || "wifi"].icon_name ??
|
|
||||||
"network-wireless-offline-symbolic";
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @param {import("../types/widgets/label.js").LabelProps} props */
|
|
||||||
export const SSIDLabel = (props) =>
|
|
||||||
Widget.Label({
|
|
||||||
truncate: "end",
|
|
||||||
...props,
|
|
||||||
}).hook(network, (self) => {
|
|
||||||
if (network.primary === "wifi")
|
|
||||||
self.label = network.wifi.ssid || "Not Connected";
|
|
||||||
else
|
|
||||||
self.label =
|
|
||||||
network.wired.internet !== "disconnected" ? "Wired" : "Not Connected";
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @param {Partial<import("../misc/menu.js").ArrowToggleButtonProps>} props */
|
|
||||||
export const Toggle = (props) =>
|
|
||||||
ArrowToggleButton({
|
|
||||||
name: "network",
|
|
||||||
icon: Indicator({ className: "qs-icon" }),
|
|
||||||
label: SSIDLabel({
|
|
||||||
max_width_chars: 20,
|
|
||||||
}),
|
|
||||||
activate: () => {
|
|
||||||
network.wifi.enabled = true;
|
|
||||||
network.wifi.scan();
|
|
||||||
},
|
|
||||||
deactivate: () => {
|
|
||||||
network.wifi.enabled = false;
|
|
||||||
},
|
|
||||||
connection: [network.wifi, () => network.wifi.enabled],
|
|
||||||
...props,
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @param {Partial<import("../misc/menu.js").MenuProps>} props */
|
|
||||||
export const Selection = (props) =>
|
|
||||||
Menu({
|
|
||||||
name: "network",
|
|
||||||
icon: Indicator({}),
|
|
||||||
title: "Network Selection",
|
|
||||||
content: [
|
|
||||||
Wired(),
|
|
||||||
Widget.Box({
|
|
||||||
vertical: true,
|
|
||||||
className: "qs-sub-sub-content",
|
|
||||||
children: network.wifi.bind("access_points").as((x) =>
|
|
||||||
x
|
|
||||||
.sort((a, b) => b.strength - a.strength)
|
|
||||||
.reduce((acc, x) => {
|
|
||||||
if (!acc.find((y) => y.ssid === x.ssid)) acc.push(x);
|
|
||||||
return acc;
|
|
||||||
}, [])
|
|
||||||
.slice(0, 10)
|
|
||||||
.map(WifiItem),
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
Widget.Separator({ className: "accent" }),
|
|
||||||
SettingsButton({ type: "wifi" }),
|
|
||||||
],
|
|
||||||
...props,
|
|
||||||
});
|
|
||||||
|
|
||||||
const Wired = () =>
|
|
||||||
Widget.Button({
|
|
||||||
// onClicked:
|
|
||||||
// visible: network.wired.bind("state").as(x => (console.log(x), true)),
|
|
||||||
child: Widget.Box({
|
|
||||||
children: [
|
|
||||||
Widget.Icon({ icon: network.wired.bind("icon_name") }),
|
|
||||||
Widget.Label("Wired"),
|
|
||||||
Widget.Icon({
|
|
||||||
icon: "object-select-symbolic",
|
|
||||||
hexpand: true,
|
|
||||||
hpack: "end",
|
|
||||||
visible: network.bind("primary").as((x) => x === "wired"),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @param {import("../types/service/network.js").Wifi["access_points"][0]} wifi */
|
|
||||||
const WifiItem = (wifi) =>
|
|
||||||
Widget.Button({
|
|
||||||
onClicked: () => Utils.execAsync(`nmcli device wifi connect ${wifi.bssid}`),
|
|
||||||
child: Widget.Box({
|
|
||||||
children: [
|
|
||||||
Widget.Icon(wifi.iconName),
|
|
||||||
Widget.Label({
|
|
||||||
truncate: "end",
|
|
||||||
max_width_chars: 28,
|
|
||||||
label: wifi.ssid || "",
|
|
||||||
}),
|
|
||||||
Widget.Icon({
|
|
||||||
icon: "object-select-symbolic",
|
|
||||||
hexpand: true,
|
|
||||||
hpack: "end",
|
|
||||||
setup: (self) =>
|
|
||||||
Utils.idle(() => {
|
|
||||||
if (!self.is_destroyed) self.visible = wifi.active;
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
@@ -1,270 +0,0 @@
|
|||||||
const notifications = await Service.import("notifications");
|
|
||||||
notifications.popupTimeout = 2000; //in seconds
|
|
||||||
notifications.forceTimeout = true; //force all notifications to timeout
|
|
||||||
|
|
||||||
/** @param {import("../types/widgets/icon").IconProps} props */
|
|
||||||
export const DNDIndicator = (props) =>
|
|
||||||
Widget.Icon({
|
|
||||||
visible: notifications.bind("dnd"),
|
|
||||||
icon: "notifications-disabled-symbolic",
|
|
||||||
...props,
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @param {import("../types/widgets/button").ButtonProps} props */
|
|
||||||
export const DNDToggle = (props) =>
|
|
||||||
Widget.Button({
|
|
||||||
onClicked: () => {
|
|
||||||
notifications.dnd = !notifications.dnd;
|
|
||||||
},
|
|
||||||
child: Widget.Icon({
|
|
||||||
icon: notifications
|
|
||||||
.bind("dnd")
|
|
||||||
.as((x) =>
|
|
||||||
x
|
|
||||||
? "preferences-system-notifications-symbolic"
|
|
||||||
: "notifications-disabled-symbolic",
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
className: notifications.bind("dnd").as((x) => (x ? "on" : "")),
|
|
||||||
...props,
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @param {import("../types/widgets/box").BoxProps} props */
|
|
||||||
export const Indicator = ({ ...props }) =>
|
|
||||||
Widget.Box({
|
|
||||||
visible: Utils.merge(
|
|
||||||
[
|
|
||||||
notifications.bind("notifications").as((x) => x.length > 0),
|
|
||||||
notifications.bind("dnd"),
|
|
||||||
],
|
|
||||||
(hasNotif, dnd) => hasNotif && !dnd,
|
|
||||||
),
|
|
||||||
children: [
|
|
||||||
Widget.Icon({ icon: "preferences-system-notifications-symbolic" }),
|
|
||||||
Widget.Revealer({
|
|
||||||
transition: "slide_right",
|
|
||||||
revealChild: notifications.bind("popups").as((x) => x.length > 0),
|
|
||||||
child: Widget.Label({
|
|
||||||
use_markup: true,
|
|
||||||
truncate: "end",
|
|
||||||
wrap: false,
|
|
||||||
label: notifications.bind("popups").as((x) => {
|
|
||||||
const notif = x[x.length - 1];
|
|
||||||
// Keep the text of the old notif for the fade out animation.
|
|
||||||
if (!notif) return old_notif;
|
|
||||||
const summary = notif.summary.substring(0, 18).trim();
|
|
||||||
const body = notif.body.substring(0, 45).trim();
|
|
||||||
old_notif = `${summary}: ${body}`.replaceAll("\n", " ");
|
|
||||||
return old_notif;
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
...props,
|
|
||||||
});
|
|
||||||
let old_notif = "";
|
|
||||||
|
|
||||||
/** @param {import("../types/service/notifications").Notification} param */
|
|
||||||
const NotificationIcon = ({ app_entry, app_icon, image }) => {
|
|
||||||
if (image) {
|
|
||||||
return Widget.Box({
|
|
||||||
vpack: "start",
|
|
||||||
hexpand: false,
|
|
||||||
className: "r20",
|
|
||||||
css: `
|
|
||||||
background-image: url("${image}");
|
|
||||||
background-size: cover;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center;
|
|
||||||
min-width: 78px;
|
|
||||||
min-height: 78px;
|
|
||||||
margin-right: 12px;
|
|
||||||
`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let icon = "dialog-information-symbolic";
|
|
||||||
if (Utils.lookUpIcon(app_icon)) icon = app_icon;
|
|
||||||
if (Utils.lookUpIcon(app_entry || "")) icon = app_entry || "";
|
|
||||||
|
|
||||||
return Widget.Box({
|
|
||||||
vpack: "start",
|
|
||||||
hexpand: false,
|
|
||||||
className: "r20",
|
|
||||||
css: `
|
|
||||||
min-width: 78px;
|
|
||||||
min-height: 78px;
|
|
||||||
margin-right: 12px;
|
|
||||||
`,
|
|
||||||
child: Widget.Icon({
|
|
||||||
icon,
|
|
||||||
size: 58,
|
|
||||||
hpack: "center",
|
|
||||||
hexpand: true,
|
|
||||||
vpack: "center",
|
|
||||||
vexpand: true,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
import GLib from "gi://GLib";
|
|
||||||
|
|
||||||
/** @param {number} time */
|
|
||||||
const time = (time, format = "%H:%M") =>
|
|
||||||
GLib.DateTime.new_from_unix_local(time).format(format);
|
|
||||||
|
|
||||||
/** @param {import("../types/service/notifications").Notification} notification */
|
|
||||||
export const Notification = (notification) => {
|
|
||||||
const content = Widget.Box({
|
|
||||||
children: [
|
|
||||||
NotificationIcon(notification),
|
|
||||||
Widget.Box({
|
|
||||||
hexpand: true,
|
|
||||||
vertical: true,
|
|
||||||
children: [
|
|
||||||
Widget.Box({
|
|
||||||
children: [
|
|
||||||
Widget.Label({
|
|
||||||
css: `
|
|
||||||
font-size: 1.1em;
|
|
||||||
margin-right: 12pt;
|
|
||||||
`,
|
|
||||||
xalign: 0,
|
|
||||||
justification: "left",
|
|
||||||
hexpand: true,
|
|
||||||
max_width_chars: 24,
|
|
||||||
truncate: "end",
|
|
||||||
wrap: true,
|
|
||||||
label: notification.summary.trim(),
|
|
||||||
use_markup: true,
|
|
||||||
}),
|
|
||||||
Widget.Label({
|
|
||||||
vpack: "start",
|
|
||||||
label: time(notification.time),
|
|
||||||
}),
|
|
||||||
Widget.Button({
|
|
||||||
css: `
|
|
||||||
margin-left: 6pt;
|
|
||||||
min-width: 1.2em;
|
|
||||||
min-height: 1.2em;
|
|
||||||
`,
|
|
||||||
vpack: "start",
|
|
||||||
child: Widget.Icon("window-close-symbolic"),
|
|
||||||
on_clicked: notification.close,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
Widget.Label({
|
|
||||||
css: "font-size: .9em;",
|
|
||||||
hexpand: true,
|
|
||||||
use_markup: true,
|
|
||||||
xalign: 0,
|
|
||||||
justification: "left",
|
|
||||||
label: notification.body.trim(),
|
|
||||||
max_width_chars: 24,
|
|
||||||
wrap: true,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
const actionsbox =
|
|
||||||
notification.actions.length > 0
|
|
||||||
? Widget.Revealer({
|
|
||||||
transition: "slide_down",
|
|
||||||
child: Widget.EventBox({
|
|
||||||
child: Widget.Box({
|
|
||||||
class_name: "actions horizontal",
|
|
||||||
children: notification.actions.map((action) =>
|
|
||||||
Widget.Button({
|
|
||||||
className: "button",
|
|
||||||
on_clicked: () => notification.invoke(action.id),
|
|
||||||
hexpand: true,
|
|
||||||
child: Widget.Label(action.label),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
: null;
|
|
||||||
|
|
||||||
const eventbox = Widget.EventBox({
|
|
||||||
vexpand: false,
|
|
||||||
on_primary_click: notification.dismiss,
|
|
||||||
on_hover() {
|
|
||||||
if (actionsbox) actionsbox.reveal_child = true;
|
|
||||||
},
|
|
||||||
on_hover_lost() {
|
|
||||||
if (actionsbox) actionsbox.reveal_child = true;
|
|
||||||
|
|
||||||
notification.dismiss();
|
|
||||||
},
|
|
||||||
child: Widget.Box({
|
|
||||||
vertical: true,
|
|
||||||
children: actionsbox ? [content, actionsbox] : [content],
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
return Widget.Box({
|
|
||||||
className: "surface r20 p10",
|
|
||||||
css: "margin: 8px 0;",
|
|
||||||
child: eventbox,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/** @param {import("../types/widgets/scrollable").ScrollableProps} props */
|
|
||||||
export const List = (props) =>
|
|
||||||
Widget.Scrollable({
|
|
||||||
vscroll: "automatic",
|
|
||||||
hscroll: "never",
|
|
||||||
css: "min-height: 500px;",
|
|
||||||
child: Widget.Box({
|
|
||||||
vertical: true,
|
|
||||||
children: notifications
|
|
||||||
.bind("notifications")
|
|
||||||
.as((x) => x.map(Notification)),
|
|
||||||
}),
|
|
||||||
...props,
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @param {import("../types/widgets/box").BoxProps} props */
|
|
||||||
export const Placeholder = (props) =>
|
|
||||||
Widget.Box({
|
|
||||||
vertical: true,
|
|
||||||
vpack: "center",
|
|
||||||
hpack: "center",
|
|
||||||
children: [
|
|
||||||
Widget.Icon({
|
|
||||||
icon: "notifications-disabled-symbolic",
|
|
||||||
size: 75,
|
|
||||||
css: "margin: 50px 0",
|
|
||||||
}),
|
|
||||||
Widget.Label({
|
|
||||||
label: "Your inbox is empty",
|
|
||||||
css: "margin-bottom: 150px;",
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
...props,
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @param {import("../types/widgets/button").ButtonProps} props */
|
|
||||||
export const ClearButton = (props) =>
|
|
||||||
Widget.Button({
|
|
||||||
className: "surface r20 p10",
|
|
||||||
onClicked: () => notifications.clear(),
|
|
||||||
sensitive: notifications.bind("notifications").as((x) => x.length > 0),
|
|
||||||
child: Widget.Box({
|
|
||||||
children: [
|
|
||||||
Widget.Label({ label: "Clear " }),
|
|
||||||
Widget.Icon({
|
|
||||||
icon: notifications
|
|
||||||
.bind("notifications")
|
|
||||||
.as((x) =>
|
|
||||||
x.length > 0 ? "user-trash-full-symbolic" : "user-trash-symbolic",
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
...props,
|
|
||||||
});
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
import { ArrowToggleButton, Menu } from "../misc/menu.js";
|
|
||||||
|
|
||||||
const powerProfiles = await Service.import("powerprofiles");
|
|
||||||
|
|
||||||
/** @param {string} x */
|
|
||||||
const capitalize = (x) => x.charAt(0).toUpperCase() + x.slice(1);
|
|
||||||
|
|
||||||
/** @param {Partial<import("../misc/menu.js").ArrowToggleButtonProps>} props */
|
|
||||||
export const Toggle = (props) =>
|
|
||||||
ArrowToggleButton({
|
|
||||||
name: "powerprofile",
|
|
||||||
icon: Widget.Icon({
|
|
||||||
icon: powerProfiles.bind("icon_name"),
|
|
||||||
className: "qs-icon",
|
|
||||||
}),
|
|
||||||
label: Widget.Label({
|
|
||||||
label: powerProfiles.bind("active_profile").as(capitalize),
|
|
||||||
truncate: "end",
|
|
||||||
max_width_chars: 20,
|
|
||||||
}),
|
|
||||||
activate: () => {},
|
|
||||||
deactivate: () => {},
|
|
||||||
connection: [powerProfiles, () => false],
|
|
||||||
...props,
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @param {Partial<import("../misc/menu.js").MenuProps>} props */
|
|
||||||
export const Selection = (props) =>
|
|
||||||
Menu({
|
|
||||||
name: "powerprofile",
|
|
||||||
icon: Widget.Icon({ icon: powerProfiles.bind("icon_name") }),
|
|
||||||
title: "Power profile",
|
|
||||||
// Hard coding the list of profiles since ags does not give them.
|
|
||||||
content: ["power-saver", "balanced", "performance"].map(ProfileItem),
|
|
||||||
...props,
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @param {string} profile */
|
|
||||||
const ProfileItem = (profile) =>
|
|
||||||
Widget.Button({
|
|
||||||
onClicked: () => (powerProfiles.active_profile = profile),
|
|
||||||
child: Widget.Box({
|
|
||||||
children: [
|
|
||||||
Widget.Icon({
|
|
||||||
icon: `power-profile-${profile}-symbolic`,
|
|
||||||
}),
|
|
||||||
Widget.Label({
|
|
||||||
label: capitalize(profile),
|
|
||||||
truncate: "end",
|
|
||||||
maxWidthChars: 28,
|
|
||||||
}),
|
|
||||||
Widget.Icon({
|
|
||||||
icon: "object-select-symbolic",
|
|
||||||
hexpand: true,
|
|
||||||
hpack: "end",
|
|
||||||
visible: powerProfiles
|
|
||||||
.bind("active_profile")
|
|
||||||
.as((x) => x === profile),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
import { icon } from "../misc/utils.js";
|
|
||||||
import { ArrowToggleButton, Menu } from "../misc/menu.js";
|
|
||||||
|
|
||||||
const systemtray = await Service.import("systemtray");
|
|
||||||
|
|
||||||
/** @param {Partial<import("../misc/menu.js").ArrowToggleButtonProps>} props */
|
|
||||||
export const Toggle = (props) =>
|
|
||||||
ArrowToggleButton({
|
|
||||||
name: "systray",
|
|
||||||
icon: Widget.Icon({ icon: "open-menu-symbolic", className: "qs-icon" }),
|
|
||||||
label: Widget.Label("Systray"),
|
|
||||||
activate: () => {},
|
|
||||||
deactivate: () => {},
|
|
||||||
connection: [systemtray, () => systemtray.items.length > 0],
|
|
||||||
...props,
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @param {Partial<import("../misc/menu.js").MenuProps>} props */
|
|
||||||
export const Selection = (props) =>
|
|
||||||
Menu({
|
|
||||||
name: "systray",
|
|
||||||
icon: Widget.Icon("open-menu-symbolic"),
|
|
||||||
title: "Systray",
|
|
||||||
content: [
|
|
||||||
Widget.Box({
|
|
||||||
vertical: true,
|
|
||||||
className: "qs-sub-sub-content",
|
|
||||||
children: systemtray.bind("items").as((i) => i.map(SysTrayItem)),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
...props,
|
|
||||||
});
|
|
||||||
|
|
||||||
/** @param {import('../types/service/systemtray.js').TrayItem} item */
|
|
||||||
const SysTrayItem = (item) =>
|
|
||||||
Widget.Button({
|
|
||||||
css: "margin: 12px;",
|
|
||||||
child: Widget.Box({
|
|
||||||
children: [
|
|
||||||
Widget.Icon({ icon: item.bind("icon").as(icon) }),
|
|
||||||
Widget.Label({
|
|
||||||
truncate: "end",
|
|
||||||
maxWidthChars: 28,
|
|
||||||
}).hook(item, (self) => {
|
|
||||||
self.label = item.title || item.tooltip_markup;
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
tooltipMarkup: item.bind("tooltip_markup"),
|
|
||||||
onPrimaryClick: (_, event) => item.activate(event),
|
|
||||||
onSecondaryClick: (_, event) => item.openMenu(event),
|
|
||||||
});
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
import Gdk from "gi://Gdk?version=3.0";
|
|
||||||
|
|
||||||
// const hyprland = await Service.import("hyprland");
|
|
||||||
//
|
|
||||||
// const display = Gdk.Display.get_default();
|
|
||||||
// /** @param {number} monitor */
|
|
||||||
// const getMonitorName = (monitor) => {
|
|
||||||
// return display.get_default_screen().get_monitor_plug_name(monitor);
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// /** @type {Record<number, boolean>} */
|
|
||||||
// const urgents = {}
|
|
||||||
//
|
|
||||||
// /** @param {{monitor: number, labels: string[]} & import("types/widgets/box").BoxProps} props */
|
|
||||||
// export const Tags = ({ monitor, labels, ...props }) => {
|
|
||||||
// const monName = getMonitorName(monitor);
|
|
||||||
// // @ts-ignore
|
|
||||||
// return Widget.Box({
|
|
||||||
// ...props,
|
|
||||||
// children: Array.from({ length: 9 }, (_, i) => i).map((i) =>
|
|
||||||
// Widget.EventBox({
|
|
||||||
// child: Widget.Label({ label: labels[i], className: "tags" }),
|
|
||||||
// attribute: i + 1,
|
|
||||||
// onPrimaryClickRelease: () => {
|
|
||||||
// hyprland.message(`dispatch workspace ${i + 1}`);
|
|
||||||
// },
|
|
||||||
// }),
|
|
||||||
// ),
|
|
||||||
// setup: (self) => {
|
|
||||||
// self.hook(hyprland, (_, address) => {
|
|
||||||
// const client = hyprland.getClient(address);
|
|
||||||
// if (!client) return;
|
|
||||||
// const ws = client.workspace.id;
|
|
||||||
// urgents[ws] = true;
|
|
||||||
// }, "urgent-window");
|
|
||||||
//
|
|
||||||
// self.hook(hyprland, () =>
|
|
||||||
// self.children.forEach((btn) => {
|
|
||||||
// const mon = hyprland.monitors.find((x) => x.name === monName);
|
|
||||||
// const ws = hyprland.workspaces.find((x) => x.id === btn.attribute);
|
|
||||||
//
|
|
||||||
// const occupied = (ws?.windows ?? 0) > 0;
|
|
||||||
// const selected = mon?.activeWorkspace?.id === btn.attribute;
|
|
||||||
// if (selected) urgents[btn.attribute] = false;
|
|
||||||
// const urgent = urgents[btn.attribute];
|
|
||||||
//
|
|
||||||
// btn.visible = occupied || selected;
|
|
||||||
// btn.class_names = [
|
|
||||||
// selected ? "accent" : "",
|
|
||||||
// urgent ? "secondary" : "",
|
|
||||||
// ];
|
|
||||||
// }),
|
|
||||||
// );
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// /** @param {{monitor: number } & import("types/widgets/label").LabelProps} props */
|
|
||||||
// export const Layout = ({ monitor, ...props }) => {
|
|
||||||
// const monName = getMonitorName(monitor);
|
|
||||||
// return Widget.Label({
|
|
||||||
// className: "module",
|
|
||||||
// ...props,
|
|
||||||
// }).hook(
|
|
||||||
// hyprland,
|
|
||||||
// (self) => {
|
|
||||||
// const mon = hyprland.monitors.find((x) => x.name === monName);
|
|
||||||
// const ws = hyprland.workspaces.find(
|
|
||||||
// (x) => x.id === mon?.activeWorkspace?.id,
|
|
||||||
// );
|
|
||||||
// self.label = ws?.windows ? `[${ws?.windows}]` : "";
|
|
||||||
// },
|
|
||||||
// "changed",
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// /** @param {{monitor: number, fallback?: string} & import("types/widgets/label").LabelProps} props */
|
|
||||||
// export const ClientLabel = ({ monitor, fallback = "", ...props }) => {
|
|
||||||
// const monName = getMonitorName(monitor);
|
|
||||||
// return Widget.Label({
|
|
||||||
// truncate: "end",
|
|
||||||
// maxWidthChars: 25,
|
|
||||||
// className: "module",
|
|
||||||
// ...props,
|
|
||||||
// }).hook(
|
|
||||||
// hyprland,
|
|
||||||
// (self) => {
|
|
||||||
// const mon = hyprland.monitors.find((x) => x.name === monName);
|
|
||||||
// const ws = hyprland.workspaces.find(
|
|
||||||
// (x) => x.id === mon?.activeWorkspace?.id,
|
|
||||||
// );
|
|
||||||
// self.label = ws?.lastwindowtitle || fallback;
|
|
||||||
// },
|
|
||||||
// "changed",
|
|
||||||
// );
|
|
||||||
// };
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
const screen = await Utils.execAsync(
|
|
||||||
"/bin/sh -c 'ls -w1 /sys/class/backlight | head -n 1'",
|
|
||||||
);
|
|
||||||
const max = Number(await Utils.execAsync("brightnessctl m"));
|
|
||||||
|
|
||||||
class Brightness extends Service {
|
|
||||||
static {
|
|
||||||
Service.register(this, {}, { screen: ["float", "rw"] });
|
|
||||||
}
|
|
||||||
|
|
||||||
#screen = 0;
|
|
||||||
|
|
||||||
get screen() {
|
|
||||||
return this.#screen;
|
|
||||||
}
|
|
||||||
|
|
||||||
set screen(percent) {
|
|
||||||
if (percent < 0) percent = 0;
|
|
||||||
if (percent > 1) percent = 1;
|
|
||||||
|
|
||||||
Utils.execAsync(`brightnessctl s ${percent * 100}% -q`)
|
|
||||||
.then(() => {
|
|
||||||
this.#screen = percent;
|
|
||||||
|
|
||||||
this.emit("changed");
|
|
||||||
this.notify("screen");
|
|
||||||
})
|
|
||||||
.catch(print);
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.#screen = Number(Utils.exec("brightnessctl g")) / max;
|
|
||||||
|
|
||||||
const screenPath = `/sys/class/backlight/${screen}/brightness`;
|
|
||||||
|
|
||||||
Utils.monitorFile(screenPath, async (f) => {
|
|
||||||
const v = await Utils.readFileAsync(f);
|
|
||||||
this.#screen = Number(v) / max;
|
|
||||||
this.changed("screen");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export default new Brightness();
|
|
||||||
@@ -1,232 +0,0 @@
|
|||||||
* {
|
|
||||||
font-family: monospace;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
all: unset;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Colors */
|
|
||||||
.transparent {
|
|
||||||
background-color: rgba(0, 0, 0, 0.6);
|
|
||||||
}
|
|
||||||
.bgcont {
|
|
||||||
background-color: #1E1E2E;
|
|
||||||
border: 1px solid #11111B;
|
|
||||||
}
|
|
||||||
.surface {
|
|
||||||
background-color: #11111B;
|
|
||||||
color: #CDD6F4;
|
|
||||||
}
|
|
||||||
.accent {
|
|
||||||
background-color: #94e2d5;
|
|
||||||
color: #1e1e2e;
|
|
||||||
}
|
|
||||||
.accent-rev {
|
|
||||||
color: #94e2d5;
|
|
||||||
background-color: #1e1e2e;
|
|
||||||
}
|
|
||||||
.secondary {
|
|
||||||
background-color: #cba6f7;
|
|
||||||
color: #1e1e2e;
|
|
||||||
}
|
|
||||||
.red {
|
|
||||||
color: #F38BA8;
|
|
||||||
}
|
|
||||||
.green {
|
|
||||||
color: #A6E3A1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Misc */
|
|
||||||
.bold {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
.f16 {
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
.round {
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
.r20 {
|
|
||||||
border-radius: 20px;
|
|
||||||
}
|
|
||||||
.r100 {
|
|
||||||
border-radius: 100px;
|
|
||||||
}
|
|
||||||
.p10 {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
separator {
|
|
||||||
border-radius: 10px;
|
|
||||||
min-height: 1px;
|
|
||||||
min-width: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Slider */
|
|
||||||
slider {
|
|
||||||
box-shadow: none;
|
|
||||||
background-color: transparent;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
transition: 200ms;
|
|
||||||
min-height: 1rem;
|
|
||||||
border-radius: 100px;
|
|
||||||
}
|
|
||||||
trough {
|
|
||||||
transition: 200ms;
|
|
||||||
border: 1px;
|
|
||||||
background-color: #000;
|
|
||||||
border-radius: 100px;
|
|
||||||
}
|
|
||||||
highlight, fill {
|
|
||||||
background-color: #94e2d5;
|
|
||||||
border-radius: 100px;
|
|
||||||
transition: 200ms;
|
|
||||||
}
|
|
||||||
mark {
|
|
||||||
background-color: #F38BA8;
|
|
||||||
min-width: 1px;
|
|
||||||
min-height: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
background-color: #1E1E2E;
|
|
||||||
border-radius: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch slider {
|
|
||||||
all: unset;
|
|
||||||
transition: 200ms;
|
|
||||||
background-color: #94e2d5;
|
|
||||||
border-radius: 100px;
|
|
||||||
min-width: 1rem;
|
|
||||||
min-height: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch image {
|
|
||||||
all: unset;
|
|
||||||
color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Bar */
|
|
||||||
.module {
|
|
||||||
padding: 1px 10px;
|
|
||||||
margin: 0 4px;
|
|
||||||
}
|
|
||||||
.tags {
|
|
||||||
min-width: 2rem;
|
|
||||||
}
|
|
||||||
.qs-item {
|
|
||||||
padding: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* On Screen Display */
|
|
||||||
.osd {
|
|
||||||
border-radius: 20px;
|
|
||||||
margin-bottom: 150px;
|
|
||||||
min-width: 175px;
|
|
||||||
min-height: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Quick settings */
|
|
||||||
.qs-container {
|
|
||||||
min-width: 400px;
|
|
||||||
padding: 25px;
|
|
||||||
margin: 10px;
|
|
||||||
border-radius: 20px;
|
|
||||||
}
|
|
||||||
.qs-button {
|
|
||||||
padding: 0px;
|
|
||||||
margin: 5px;
|
|
||||||
border-radius: 20px;
|
|
||||||
min-width: 225px;
|
|
||||||
min-height: 75px;
|
|
||||||
transition: background-color 0.3s ease-in-out;
|
|
||||||
transition: color 0.3s ease-in-out;
|
|
||||||
}
|
|
||||||
.qs-icon {
|
|
||||||
padding: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qs-slider > * {
|
|
||||||
margin: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qs-submenu {
|
|
||||||
margin-top: 8px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
padding: 12px;
|
|
||||||
border-radius: 20px;
|
|
||||||
}
|
|
||||||
.qs-sub-title {
|
|
||||||
padding: 14px 20px;
|
|
||||||
border-radius: 100px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
.qs-sub-title > *:first-child {
|
|
||||||
margin-right: 14px;
|
|
||||||
}
|
|
||||||
.qs-sub-content image {
|
|
||||||
margin-right: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qs-sub-content > * {
|
|
||||||
margin: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.qs-sub-sub-content > * {
|
|
||||||
margin: 6px 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar {
|
|
||||||
border-radius: 50%;
|
|
||||||
min-width: 70px;
|
|
||||||
min-height: 70px;
|
|
||||||
background-size: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sys-button {
|
|
||||||
min-width: 40px;
|
|
||||||
min-height: 40px;
|
|
||||||
border-radius: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpris-cover-art {
|
|
||||||
background-size: cover;
|
|
||||||
background-position: center;
|
|
||||||
margin: 10px 0;
|
|
||||||
min-height: 205px;
|
|
||||||
border-radius: 20px;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
.mpris-cover-art > * {
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
.mpris-play {
|
|
||||||
min-width: 60px;
|
|
||||||
min-height: 60px;
|
|
||||||
border-radius: 20px;
|
|
||||||
padding: 0;
|
|
||||||
transition: all 0.3s;
|
|
||||||
}
|
|
||||||
.mpris-position-slider {
|
|
||||||
margin: 0 10px;
|
|
||||||
}
|
|
||||||
.mpris-position-slider trough {
|
|
||||||
border-radius: 100px;
|
|
||||||
background-color: rgba(255, 255, 255, 0.6);
|
|
||||||
min-height: 4px;
|
|
||||||
}
|
|
||||||
.mpris-position-slider highlight, .mpris-position-slider progress {
|
|
||||||
background-color: #fff;
|
|
||||||
border-radius: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mpris-position-slider slider {
|
|
||||||
border-radius: 100px;
|
|
||||||
min-height: 4px;
|
|
||||||
min-width: 4px;
|
|
||||||
background-color: #fff;
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "ES2022",
|
|
||||||
"module": "ES2022",
|
|
||||||
"lib": [
|
|
||||||
"ES2022"
|
|
||||||
],
|
|
||||||
"allowJs": true,
|
|
||||||
"checkJs": true,
|
|
||||||
"strict": true,
|
|
||||||
"noImplicitAny": false,
|
|
||||||
"baseUrl": ".",
|
|
||||||
"typeRoots": [
|
|
||||||
"./types"
|
|
||||||
],
|
|
||||||
"skipLibCheck": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
/nix/store/g5s50dy9za9inrw5zps05s7kh4ypnbpa-ags-1.8.2/share/com.github.Aylur.ags/types
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
pkgs,
|
pkgs,
|
||||||
lib,
|
lib,
|
||||||
|
noctalia,
|
||||||
...
|
...
|
||||||
}: let
|
}: let
|
||||||
wallpaper = pkgs.writeShellScriptBin "wallpaper" ''
|
wallpaper = pkgs.writeShellScriptBin "wallpaper" ''
|
||||||
@@ -39,9 +40,9 @@
|
|||||||
in {
|
in {
|
||||||
imports = [
|
imports = [
|
||||||
./rofi
|
./rofi
|
||||||
./quickshell
|
|
||||||
./fcitx5.nix
|
./fcitx5.nix
|
||||||
./hyprlock.nix
|
./hyprlock.nix
|
||||||
|
noctalia.homeModules.default
|
||||||
];
|
];
|
||||||
home.packages = [
|
home.packages = [
|
||||||
wallpaper
|
wallpaper
|
||||||
@@ -89,4 +90,127 @@ in {
|
|||||||
button-layout = "";
|
button-layout = "";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
programs.noctalia-shell = {
|
||||||
|
enable = true;
|
||||||
|
systemd.enable = true;
|
||||||
|
settings = {
|
||||||
|
bar = {
|
||||||
|
capsuleOpacity = 0.5;
|
||||||
|
showCapsule = false;
|
||||||
|
outerCorners = false;
|
||||||
|
widgets = {
|
||||||
|
left = [
|
||||||
|
{
|
||||||
|
id = "TaskbarGrouped";
|
||||||
|
hideUnoccupied = true;
|
||||||
|
labelMode = "none";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
center = [
|
||||||
|
{
|
||||||
|
id = "NotificationHistory";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
right = [
|
||||||
|
{
|
||||||
|
id = "MediaMini";
|
||||||
|
maxWidth = 250;
|
||||||
|
showArtistFirst = false;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
id = "Spacer";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
id = "Tray";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
id = "Battery";
|
||||||
|
displayMode = "alwaysShow";
|
||||||
|
warningThreshold = 30;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
id = "Volume";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
id = "Bluetooth";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
id = "WiFi";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
id = "Spacer";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
id = "Clock";
|
||||||
|
formatHorizontal = "HH:mm\\nyyyy-MM-dd";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
controlCenter = {
|
||||||
|
position = "top_center";
|
||||||
|
};
|
||||||
|
notifications = {
|
||||||
|
enabled = true;
|
||||||
|
location = "bar";
|
||||||
|
lowUrgencyDuration = 3;
|
||||||
|
normalUrgencyDuration = 3;
|
||||||
|
criticalUrgencyDuration = 3;
|
||||||
|
};
|
||||||
|
osd = {
|
||||||
|
enabled = true;
|
||||||
|
location = "bottom";
|
||||||
|
};
|
||||||
|
sessionMenu = {
|
||||||
|
enableCountdown = true;
|
||||||
|
position = "top_right";
|
||||||
|
powerOptions = [
|
||||||
|
{
|
||||||
|
action = "lock";
|
||||||
|
countdownEnabled = false;
|
||||||
|
enabled = true;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
action = "suspend";
|
||||||
|
countdownEnabled = false;
|
||||||
|
enabled = true;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
action = "hibernate";
|
||||||
|
countdownEnabled = true;
|
||||||
|
enabled = true;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
action = "reboot";
|
||||||
|
countdownEnabled = true;
|
||||||
|
enabled = true;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
action = "logout";
|
||||||
|
countdownEnabled = false;
|
||||||
|
enabled = true;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
action = "shutdown";
|
||||||
|
countdownEnabled = true;
|
||||||
|
enabled = true;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
showHeader = true;
|
||||||
|
};
|
||||||
|
settingsVersion = 23;
|
||||||
|
setupCompleted = true;
|
||||||
|
general = {
|
||||||
|
lockOnSuspend = false;
|
||||||
|
showScreenCorners = false;
|
||||||
|
};
|
||||||
|
colorSchemes = {
|
||||||
|
predefinedScheme = "Catppuccin";
|
||||||
|
};
|
||||||
|
wallpaper.enabled = false;
|
||||||
|
dock.enabled = false;
|
||||||
|
nightLight.enabled = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
UseTabs=true
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
/run/user/1000/quickshell/vfs/61f729b565ae1eaaa7b71145d10755fe/.qmlls.ini
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
programs.quickshell = {
|
|
||||||
enable = true;
|
|
||||||
systemd.enable = true;
|
|
||||||
configs = {
|
|
||||||
default = ./.;
|
|
||||||
};
|
|
||||||
activeConfig = "default";
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import Quickshell
|
|
||||||
import QtQuick
|
|
||||||
import qs.widgets
|
|
||||||
|
|
||||||
|
|
||||||
ShellRoot {
|
|
||||||
Bar {}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
function onReloadCompleted() {
|
|
||||||
Quickshell.inhibitReloadPopup();
|
|
||||||
}
|
|
||||||
|
|
||||||
target: Quickshell
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
pragma Singleton
|
|
||||||
|
|
||||||
import Quickshell
|
|
||||||
import QtQuick
|
|
||||||
|
|
||||||
Singleton {
|
|
||||||
id: root
|
|
||||||
property QtObject colors
|
|
||||||
|
|
||||||
colors: QtObject {
|
|
||||||
readonly property color base: "#313244"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
import Quickshell
|
|
||||||
import QtQuick
|
|
||||||
|
|
||||||
import qs.utils
|
|
||||||
|
|
||||||
Scope {
|
|
||||||
Variants {
|
|
||||||
model: Quickshell.screens
|
|
||||||
|
|
||||||
delegate: Component {
|
|
||||||
PanelWindow {
|
|
||||||
required property var modelData
|
|
||||||
|
|
||||||
screen: modelData
|
|
||||||
anchors {
|
|
||||||
top: true
|
|
||||||
left: true
|
|
||||||
right: true
|
|
||||||
}
|
|
||||||
color: Settings.colors.base
|
|
||||||
|
|
||||||
implicitHeight: 30
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
|
|
||||||
text: Qt.formatDateTime(clock.date, "hh:mm")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SystemClock {
|
|
||||||
id: clock
|
|
||||||
precision: SystemClock.Minutes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user