begin astal rewrite in lua; implement workspace widget in statusbar

This commit is contained in:
gnat 2024-10-12 00:46:39 -07:00
parent 9e70891175
commit 9d44a0ef14
42 changed files with 161 additions and 2848 deletions

View File

@ -1,64 +0,0 @@
const { execAsync } = Utils
// import { reveal_menu, reveal_launcher, Bar, FakeBar } from './windows/bar.js'
import { Bar, FakeBar } from './windows/top-bar.js'
import { NotificationPopups } from './windows/notifications.js'
import { Lock, show_lock } from './windows/lock.js'
execAsync('mpDris2')
Utils.monitorFile(
`./style/style.scss`,
function() {
const scss = `./style/style.scss`
const css = `./style/style.css`
Utils.exec(`sassc ${scss} ${css}`)
App.resetCss()
App.applyCss(css)
},
)
App.addIcons('/usr/share/icons/Papirus/symbolic/status')
App.config({
windows: [
Lock,
FakeBar,
Bar,
NotificationPopups,
],
style: './style/style.css',
icons: './icons'
})
Object.defineProperty(globalThis, "lock", {
get() {
show_lock.value = true
}
})
/*
Object.defineProperty(globalThis, "swipeRight", {
get() {
if (reveal_menu.value) {
reveal_launcher.value = true
} else {
reveal_menu.value = true
}
}
})
Object.defineProperty(globalThis, "swipeLeft", {
get() {
if (reveal_launcher.value) {
reveal_launcher.value = false
} else {
reveal_menu.value = false
}
}
})*/

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M2,7V8.5H3V17H4.5V7C3.7,7 2.8,7 2,7M6,7V7L6,16H7V17H14V16H22V7H6M17.5,9A2.5,2.5 0 0,1 20,11.5A2.5,2.5 0 0,1 17.5,14A2.5,2.5 0 0,1 15,11.5A2.5,2.5 0 0,1 17.5,9Z" /></svg>

Before

Width:  |  Height:  |  Size: 238 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12,11A1,1 0 0,0 11,12A1,1 0 0,0 12,13A1,1 0 0,0 13,12A1,1 0 0,0 12,11M12.5,2C17,2 17.11,5.57 14.75,6.75C13.76,7.24 13.32,8.29 13.13,9.22C13.61,9.42 14.03,9.73 14.35,10.13C18.05,8.13 22.03,8.92 22.03,12.5C22.03,17 18.46,17.1 17.28,14.73C16.78,13.74 15.72,13.3 14.79,13.11C14.59,13.59 14.28,14 13.88,14.34C15.87,18.03 15.08,22 11.5,22C7,22 6.91,18.42 9.27,17.24C10.25,16.75 10.69,15.71 10.89,14.79C10.4,14.59 9.97,14.27 9.65,13.87C5.96,15.85 2,15.07 2,11.5C2,7 5.56,6.89 6.74,9.26C7.24,10.25 8.29,10.68 9.22,10.87C9.41,10.39 9.73,9.97 10.14,9.65C8.15,5.96 8.94,2 12.5,2Z" /></svg>

Before

Width:  |  Height:  |  Size: 648 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M64 64C28.7 64 0 92.7 0 128v7.4c0 6.8 4.4 12.6 10.1 16.3C23.3 160.3 32 175.1 32 192s-8.7 31.7-21.9 40.3C4.4 236 0 241.8 0 248.6V320H576V248.6c0-6.8-4.4-12.6-10.1-16.3C552.7 223.7 544 208.9 544 192s8.7-31.7 21.9-40.3c5.7-3.7 10.1-9.5 10.1-16.3V128c0-35.3-28.7-64-64-64H64zM576 352H0v64c0 17.7 14.3 32 32 32H80V416c0-8.8 7.2-16 16-16s16 7.2 16 16v32h96V416c0-8.8 7.2-16 16-16s16 7.2 16 16v32h96V416c0-8.8 7.2-16 16-16s16 7.2 16 16v32h96V416c0-8.8 7.2-16 16-16s16 7.2 16 16v32h48c17.7 0 32-14.3 32-32V352zM192 160v64c0 17.7-14.3 32-32 32s-32-14.3-32-32V160c0-17.7 14.3-32 32-32s32 14.3 32 32zm128 0v64c0 17.7-14.3 32-32 32s-32-14.3-32-32V160c0-17.7 14.3-32 32-32s32 14.3 32 32zm128 0v64c0 17.7-14.3 32-32 32s-32-14.3-32-32V160c0-17.7 14.3-32 32-32s32 14.3 32 32z"/></svg>

Before

Width:  |  Height:  |  Size: 990 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M176 24c0-13.3-10.7-24-24-24s-24 10.7-24 24V64c-35.3 0-64 28.7-64 64H24c-13.3 0-24 10.7-24 24s10.7 24 24 24H64v56H24c-13.3 0-24 10.7-24 24s10.7 24 24 24H64v56H24c-13.3 0-24 10.7-24 24s10.7 24 24 24H64c0 35.3 28.7 64 64 64v40c0 13.3 10.7 24 24 24s24-10.7 24-24V448h56v40c0 13.3 10.7 24 24 24s24-10.7 24-24V448h56v40c0 13.3 10.7 24 24 24s24-10.7 24-24V448c35.3 0 64-28.7 64-64h40c13.3 0 24-10.7 24-24s-10.7-24-24-24H448V280h40c13.3 0 24-10.7 24-24s-10.7-24-24-24H448V176h40c13.3 0 24-10.7 24-24s-10.7-24-24-24H448c0-35.3-28.7-64-64-64V24c0-13.3-10.7-24-24-24s-24 10.7-24 24V64H280V24c0-13.3-10.7-24-24-24s-24 10.7-24 24V64H176V24zM160 128H352c17.7 0 32 14.3 32 32V352c0 17.7-14.3 32-32 32H160c-17.7 0-32-14.3-32-32V160c0-17.7 14.3-32 32-32zm192 32H160V352H352V160z"/></svg>

Before

Width:  |  Height:  |  Size: 993 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M15 13V5A3 3 0 0 0 9 5V13A5 5 0 1 0 15 13M12 4A1 1 0 0 1 13 5V8H11V5A1 1 0 0 1 12 4Z" /></svg>

Before

Width:  |  Height:  |  Size: 163 B

17
init.lua Normal file
View File

@ -0,0 +1,17 @@
require("lua.globals")
local bar <const> = require('lua.widgets.bar')
local App <const> = astal.App
local scss <const> = "./style/style.scss"
local css <const> = "./style/style.css"
astal.exec(string.format("sassc %s %s", scss, css))
App:start({
css = css,
main = function()
bar:show_all()
end,
})

15
lua/globals.lua Normal file
View File

@ -0,0 +1,15 @@
lgi = require('lgi')
astal = require("astal")
GLib = astal.GLib
Gtk = astal.Gtk
Gdk = astal.Gdk
Gio = astal.Gio
Astal = astal.Astal
Widget = astal.Widget
bind = astal.bind
Variable = astal.Variable

23
lua/lib/init.lua Normal file
View File

@ -0,0 +1,23 @@
local M = {}
function M.map(tbl, f)
local t = {}
for k,v in pairs(tbl) do
t[k] = f(v)
end
return t
end
function M.sequence(start, stop)
local t = {}
for i=start,stop do
table.insert(t, i)
end
return t
end
function ternary(cond, t, f)
if cond then return t else return f end
end
return M

9
lua/widgets/bar/init.lua Normal file
View File

@ -0,0 +1,9 @@
local workspaces <const> = require(... .. '.workspaces')
return Astal.Window({
namespace = "bar",
name = "bar",
anchor = Astal.WindowAnchor.TOP + Astal.WindowAnchor.LEFT + Astal.WindowAnchor.RIGHT,
exclusivity = "EXCLUSIVE",
child = workspaces,
})

View File

@ -0,0 +1,48 @@
local Hyprland <const> = astal.require("AstalHyprland")
local map, sequence = require('lua.lib').map, require('lua.lib').sequence
local hypr = Hyprland.get_default()
local map, sequence = require('lua.lib').map, require('lua.lib').sequence
local hypr = Hyprland.get_default()
local function workspace_row(start, stop)
return Widget.Box({
children = map(sequence(start, stop), function(i)
return Widget.Button({
setup = function(self)
self:hook(hypr, 'notify::focused-workspace', function(self, workspace)
self:toggle_class_name('focused', workspace:get_id() == i)
end)
self:hook(hypr, 'notify::workspaces', function(self, workspaces)
map(workspaces, function(workspace)
if workspace:get_id() == i then
-- ick
local count = 0
for _ in ipairs(workspace:get_clients()) do
count = count + 1
end
self:toggle_class_name('occupied', workspace:get_id() == i and count > 0)
end
end)
end)
end,
on_click_release = function()
hypr:message_async(string.format("dispatch workspace %d", i))
end
})
end)
})
end
return Widget.Box({
class_name = 'workspaces',
vertical = true,
hexpand = false,
halign = 'START',
children = {
workspace_row(1, 5),
workspace_row(6, 10),
}
})

View File

@ -1,44 +0,0 @@
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.changed("screen");
})
.catch(console.error);
}
constructor() {
super();
try {
this._screen =
Number(Utils.exec("brightnessctl g")) /
Number(Utils.exec("brightnessctl m"));
} catch (error) {
console.error("missing dependancy: brightnessctl");
}
}
}
const service = new Brightness();
export default service;

View File

@ -1,361 +0,0 @@
import Gio from "gi://Gio";
Gio._promisify(Gio.DataInputStream.prototype, "read_line_async");
class Mpd extends Service {
static {
Service.register(
this,
{},
{
//TODO: parse some properties like duration into number?
partition: ["string", "r"],
volume: ["string", "r"],
repeat: ["string", "r"],
random: ["string", "r"],
single: ["string", "r"],
consume: ["string", "r"],
playlist: ["string", "r"],
playlistlength: ["string", "r"],
state: ["string", "r"],
song: ["string", "r"],
songid: ["string", "r"],
nextsong: ["string", "r"],
nextsongid: ["string", "r"],
elapsed: ["string", "r"],
duration: ["string", "r"],
bitrate: ["string", "r"],
mixrampdb: ["string", "r"],
audio: ["string", "r"],
file: ["string", "r"],
"Last-Modified": ["string", "r"],
Artist: ["string", "r"],
Title: ["string", "r"],
Album: ["string", "r"],
Pos: ["string", "r"],
Id: ["string", "r"],
},
);
}
#socket;
#inputStream;
#outputStream;
_decoder = new TextDecoder();
_encoder = new TextEncoder();
_messageHandlerQueue = [];
//TODO: more properties?
// Status
_partition;
_volume;
_repeat;
_random;
_single;
_consume;
_playlist;
_playlistlength;
_state;
_song;
_songid;
_nextsong;
_nextsongid;
_elapsed;
_duration;
_bitrate;
_mixrampdb;
_audio;
_file;
_LastModified;
_Artist;
_Title;
_Album;
_Pos;
_Id;
get partition() {
return this._partition;
}
get volume() {
return this._volume;
}
get repeat() {
return this._repeat;
}
get random() {
return this._random;
}
get single() {
return this._single;
}
get consume() {
return this._consume;
}
get playlist() {
return this._playlist;
}
get playlistlength() {
return this._playlistlength;
}
get state() {
return this._state;
}
get song() {
return this._song;
}
get songid() {
return this._songid;
}
get nextsong() {
return this._nextsong;
}
get nextsongid() {
return this._nextsongid;
}
get elapsed() {
return this._elapsed;
}
get duration() {
return this._duration;
}
get bitrate() {
return this._bitrate;
}
get mixrampdb() {
return this._mixrampdb;
}
get audio() {
return this._audio;
}
get file() {
return this._file;
}
get Last_Modified() {
return this._LastModified;
}
get Artist() {
return this._Artist;
}
get Title() {
return this._Title;
}
get Album() {
return this._Album;
}
get Pos() {
return this._Pos;
}
get Id() {
return this._Id;
}
constructor() {
super();
this._initSocket();
}
async _initSocket() {
try {
this.#socket = new Gio.SocketClient().connect_to_host(
"localhost",
6600,
null,
);
this.#inputStream = new Gio.DataInputStream({
base_stream: this.#socket.get_input_stream(),
});
this.#outputStream = new Gio.DataOutputStream({
base_stream: this.#socket.get_output_stream(),
});
this._watchSocket();
//init properties
//[TODO): init more properties?
this.send("status")
.then(this._updateProperties.bind(this))
.catch(logError);
this.send("currentsong")
.then(this._updateProperties.bind(this))
.catch(logError);
} catch (e) {
logError(e);
}
}
async _watchSocket() {
let bufferedLines = [];
while (true) {
const [rawData] = await this.#inputStream.read_line_async(0, null);
const data = this._decoder.decode(rawData);
if (data == null) continue;
bufferedLines.push(data);
const { response, remain } = this._parseResponse(bufferedLines);
bufferedLines = remain;
if (!response) continue;
switch (response.type) {
case "version":
console.log(`MPD Server Version ${response.payload}`);
break;
case "error":
this._handleMessage(new Error(response.payload), null);
break;
case "data":
this._handleMessage(null, response.payload);
break;
}
}
}
_parseResponse(lines) {
let response;
let beginLine = 0;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const version = line.match(/^OK MPD (.+)/);
const error = line.match(/^ACK \[.*] {.*} (.+)/);
if (version) {
response = { type: "version", payload: version[1] };
beginLine = i + 1;
} else if (error) {
response = { type: "error", payload: error[1] };
beginLine = i + 1;
} else if (line === "OK") {
response = {
type: "data",
payload: lines.slice(beginLine, i).join("\n"),
};
beginLine = i + 1;
}
}
return { response, remain: lines.slice(beginLine) };
}
_handleMessage(err, msg) {
const { func } = this._messageHandlerQueue.shift();
func(err, msg);
if (this._messageHandlerQueue.length === 0) {
this._idle();
}
}
async send(data) {
data = data.trim();
const isIdle = data === "idle";
if (this._messageHandlerQueue[0]?.isIdle) {
this.#outputStream.write(this._encoder.encode("noidle\n"), null);
}
this.#outputStream.write(this._encoder.encode(`${data}\n`), null);
return new Promise((resolve, reject) => {
this._messageHandlerQueue.push({
isIdle,
func: (err, msg) => {
if (err != null) reject(err);
resolve(msg);
},
});
});
}
_idle() {
this.send("idle")
.then((msg) => {
for (const line of msg.split("\n")) {
const subsystem = /changed: (\w+)/.exec(line);
if (subsystem == null) continue;
//TODO: only update those things that could have
//changed using a switch over the subsystems
this.send("status")
.then(this._updateProperties.bind(this))
.catch(logError);
this.send("currentsong")
.then(this._updateProperties.bind(this))
.catch(logError);
/*
switch(subsystem[1]) {
case "player":
break;
}*/
}
})
.catch(logError);
}
_updateProperties(msg) {
for (const line of msg.split("\n")) {
const keyValue = line.match(/(.*): (.*)/);
if (keyValue == null) continue;
const deprecatedKeys = [
"time",
"Time", //deprecated
"Format", //same as audio
];
if (deprecatedKeys.includes(keyValue[1])) continue;
if (!this.hasOwnProperty(`_${keyValue[1]}`)) continue;
this.updateProperty(keyValue[1], keyValue[2]);
this.emit("changed");
}
}
setCrossfade = (seconds) => this.send(`crossfade ${seconds}`);
setVolume = (volume) => this.send(`setvol ${volume}`);
toggleShuffle = () => this.send(`random ${+this._random ? "0" : "1"}`);
toggleRepeat = () => this.send(`repeat ${+this._repeat ? "0" : "1"}`);
next = () => this.send("next");
playPause = () => this.send(`pause ${this._state === "pause" ? "0" : "1"}`);
pause = () => this.send("pause 1");
play = () => this.send("pause 0");
playSong = (songpos) => this.send(`play ${songpos}`);
playSongId = (songid) => this.send(`playid ${songid}`);
seekSong = (songpos, time) => this.send(`seek ${songpos} ${time}`);
seekSongId = (songid, time) => this.send(`seekid ${songid} ${time}`);
seekCur = (time) => this.send(`seekcur ${time}`);
previous = () => this.send("previous");
stop = () => this.send("stop");
clearQueue = () => this.send("clear");
}
const service = new Mpd;
export default service;

View File

@ -1,68 +0,0 @@
#status-bar .Menu {
background-color: $bg-alt-1;
min-width: 640px;
.mpd-controls {
background-color: $bg;
border-radius: 10px;
margin: 10px;
.button {
font-size: 24px;
padding: 10px;
margin: 10px;
background-color: $bg-alt-1;
border-color: $bg-alt-1;
border-radius: 10px;
}
.cover {
min-width: 250px;
min-height: 250px;
border-radius: 10px;
margin: 10px;
background: $bg;
background-size: cover;
}
.title {
padding-top: 10px;
font-size: 20px;
font-weight: bold;
}
.position {
margin: 10px;
margin-right: 20px;
min-height: 10px;
border-radius: 5px;
trough {
background: $bg-alt-1;
border-radius: 5px;
min-height: 10px;
}
slider {
min-height: 10px;
border-radius: 5px;
background: $mpd-progress-primary;
}
}
.position-label {
margin-top: 75px;
}
}
.dial-parent {
@include panel-dial($resource-dial-fg-cpu, $bg);
border-radius: 10px;
.dial-icon {
color: $fg;
}
}
}

View File

@ -1,75 +0,0 @@
* :not(selection) :not(tooltip) {
all: unset;
}
#status-bar {
background-color: $bg;
border-radius: 0 10px 10px 0;
.dial-container {
margin-top: 10px;
.battery-dial {
@include bar-dial($battery-dial-bg, $battery-dial-fg)
}
.volume-dial {
@include bar-dial($volume-dial-bg, $volume-dial-fg)
}
.brightness-dial {
@include bar-dial($brightness-dial-bg, $brightness-dial-fg)
}
}
.workspace-container {
background-color: $bg-alt-1;
border-radius: 10px;
padding: 6px 0px;
margin: 0px 12px;
border: none;
.ws-norm {
min-width: 15px;
border-radius: 10px;
margin: 2px 5px;
padding: 6px 10px;
background-color: $ws-inactive;
color: $fg;
}
.ws-active {
color: $bg;
background-color: $ws-active;
}
}
.menu-visibility-button {
border-radius: 0px 10px 10px 0px;
padding: 10px 0px 10px 2px;
background-color: $bg-alt-1
}
.clock {
background-color: $bg-alt-1;
margin: 8px;
margin-bottom: 10px;
padding: 6px 2px;
border-radius: 10px;
}
.mpd-controls {
.button {
font-size: 14px;
margin: 2px;
margin-bottom: 8px;
font-family: 'Symbols Nerd Font Mono';
&:hover {
color: $button-hover-color;
}
}
}
}

View File

@ -1,43 +0,0 @@
.launcher {
min-width: 640px;
background: $bg-alt-1;
.search {
> image {
margin-left: 10px;
}
> entry {
padding: 5px;
margin: 10px;
background: $bg;
border: 5px solid $bg;
border-radius: 10px;
font-size: 32px;
}
}
.entry {
margin: 0 10px 0 10px;
border: 10px solid $bg;
border-radius: 10px;
background: $bg;
> box {
> image {
padding: 5px;
margin: 5px 10px 5px 10px;
background: $bg-alt-1;
border-radius: 5px;
}
> label {
}
}
}
> scrolledwindow > * > box:last-child {
margin-bottom: 10px;
}
}

View File

@ -1,28 +0,0 @@
.lock-ready > box{
box {
background: $bg;
padding: 20px;
border: 10px solid $hl-alt-1;
min-width: 300px;
margin: 15px;
}
.img {
background: url('/home/catalie/.config/wallpaper');
min-width: 300px;
min-height: 300px;
background-position: 25% 12.5%;
background-size: cover;
}
box > entry {
background: $bg-alt-1;
font-size: 14px;
padding: 5px;
margin: 10px;
}
}
.lock {
background: transparent;
}

View File

@ -1,98 +0,0 @@
#notifications {
background: transparent;
}
.notifications {
opacity: 1;
min-width: 24rem;
.revealer {
+ .revealer {
>*>box {
margin-top: 0;
}
}
.notification {
margin-bottom: 25px;
min-width: 24rem;
padding: 0px;
border: 1px solid $fg;
border-right: none;
background: $bg;
}
.urgency-indicator {
min-width: 15px;
margin-bottom: 25px;
&.normal, &.low {
background: $hl-alt-2;
border: 1px solid $hl-alt-2;
}
&.urgent {
background: $hl;
border: 1px solid $hl
}
}
.body {
margin-right: 1em;
}
.timeout-bar {
background: $bg-alt-1;
> trough > progress {
background-image: none;
background-color: $hl-alt-1;
}
}
.button {
font-size: 20px;
margin-left: 5px;
margin-right: 5px;
&:hover {
color: $hl;
}
}
.icon {
min-width: 68px;
min-height: 68px;
margin: 5px;
}
.icon image {
font-size: 58px;
margin: 5px;
color: $fg;
}
.icon box {
min-width: 68px;
min-height: 68px;
}
.title {
font-size: 28px;
padding-right: 5px;
}
.actions .action-button {
margin: 0 .4em;
margin-top: .8em;
border: 2px solid $fg;
}
.actions .action-button:first-child {
margin-left: .5em;
}
.actions .action-button:last-child {
margin-right: .5em;
}
}
}

View File

@ -1,126 +0,0 @@
$height: 50px;
#status-bar {
background: transparent;
.workspaces {
min-height: $height;
background: $bg;
padding: 5px;
padding-top: 15px;
.workspace {
min-width: 10px;
min-height: 10px;
margin: 5px;
background-color: $bg-alt-1;
}
.occupied {
background-color: $bg-alt-2;
}
.focused {
background-color: $hl;
}
}
.media-controls {
margin-top: 20px;
min-width: 220px;
.button {
font-size: 20px;
}
}
.progress-bar {
margin-top: 20px;
min-width: 200px;
min-height: 2px;
background: transparent;
trough {
min-height: 2px;
background: $bg-alt-1;
}
highlight {
min-height: 2px;
background: $mpd-progress-primary;
}
}
.right {
min-height: $height;
background: $bg;
.battery-container {
padding-left: 10px;
padding-right: 10px;
.battery-dial {
@include bar-dial($battery-dial-bg, $battery-dial-fg);
}
}
.sliderbox {
margin: 5px;
.volume {
.slider {
trough {
min-height: 10px;
min-width: 120px;
background: $bg-alt-1;
}
highlight {
background: $hl-alt-1;
}
}
button {
font-size: 20px;
margin: 5px;
}
}
.brightness {
.slider {
trough {
min-height: 10px;
min-width: 120px;
background: $bg-alt-1;
}
highlight {
background: $hl-alt-1;
}
}
button {
font-size: 20px;
margin: 5px;
}
}
}
separator {
background: $bg-alt-1;
padding: 1px;
margin-top: 10px;
margin-bottom: 10px;
}
.clock {
background: $bg;
.datetime {
margin: 1px;
font-size: 10pt;
}
}
}
}

212
style/css
View File

@ -1,212 +0,0 @@
* :not(selection) :not(tooltip) {
all: unset; }
#status-bar {
background-color: #161616;
border-radius: 0 10px 10px 0; }
#status-bar .dial-container {
margin-top: 10px; }
#status-bar .dial-container .battery-dial {
color: #33B1FF;
background-color: #161616;
margin: 0px;
padding: 0px;
font-size: 5px; }
#status-bar .dial-container .battery-dial .dial-icon {
font-family: 'Symbols Nerd Font Mono';
font-size: 16px;
color: #f2f4f8; }
#status-bar .dial-container .volume-dial {
color: #33B1FF;
background-color: #161616;
margin: 0px;
padding: 0px;
font-size: 5px; }
#status-bar .dial-container .volume-dial .dial-icon {
font-family: 'Symbols Nerd Font Mono';
font-size: 16px;
color: #f2f4f8; }
#status-bar .dial-container .brightness-dial {
color: #33B1FF;
background-color: #161616;
margin: 0px;
padding: 0px;
font-size: 5px; }
#status-bar .dial-container .brightness-dial .dial-icon {
font-family: 'Symbols Nerd Font Mono';
font-size: 16px;
color: #f2f4f8; }
#status-bar .workspace-container {
background-color: #262626;
border-radius: 10px;
padding: 6px 0px;
margin: 0px 12px;
border: none; }
#status-bar .workspace-container .ws-norm {
min-width: 15px;
border-radius: 10px;
margin: 2px 5px;
padding: 6px 10px;
background-color: #161616;
color: #f2f4f8; }
#status-bar .workspace-container .ws-active {
color: #161616;
background-color: #FF7EB6; }
#status-bar .menu-visibility-button {
border-radius: 0px 10px 10px 0px;
padding: 10px 0px 10px 2px;
background-color: #262626; }
#status-bar .clock {
background-color: #262626;
margin: 8px;
margin-bottom: 10px;
padding: 6px 2px;
border-radius: 10px; }
#status-bar .mpd-controls .button {
font-size: 14px;
margin: 2px;
margin-bottom: 8px;
font-family: 'Symbols Nerd Font Mono'; }
#status-bar .mpd-controls .button:hover {
color: #FF7EB6; }
#status-bar .Menu {
background-color: #262626;
min-width: 640px; }
#status-bar .Menu .mpd-controls {
background-color: #161616;
border-radius: 10px;
margin: 10px; }
#status-bar .Menu .mpd-controls .button {
font-size: 24px;
padding: 10px;
margin: 10px;
background-color: #262626;
border-color: #262626;
border-radius: 10px; }
#status-bar .Menu .mpd-controls .cover {
min-width: 250px;
min-height: 250px;
border-radius: 10px;
margin: 10px;
background: #161616;
background-size: cover; }
#status-bar .Menu .mpd-controls .title {
padding-top: 10px;
font-size: 20px;
font-weight: bold; }
#status-bar .Menu .mpd-controls .position {
margin: 10px;
margin-right: 20px;
min-height: 10px;
border-radius: 5px; }
#status-bar .Menu .mpd-controls .position trough {
background: #262626;
border-radius: 5px;
min-height: 10px; }
#status-bar .Menu .mpd-controls .position highlight {
min-height: 10px;
border-radius: 5px;
background: #FF7Eb6; }
#status-bar .Menu .mpd-controls .position-label {
margin-top: 75px; }
#status-bar .Menu .dial-parent {
color: #FF7eb6;
background-color: #161616;
margin: 0px;
padding: 10px;
font-size: 8px;
margin-left: 10px;
border-radius: 10px;
border-radius: 10px; }
#status-bar .Menu .dial-parent .dial-icon {
font-family: 'Symbols Nerd Font Mono';
font-size: 32px;
color: #f2f4f8; }
#status-bar .Menu .dial-parent .resource-dial {
min-width: 96px;
min-height: 96px; }
#status-bar .Menu .dial-parent .dial-icon {
color: #f2f4f8; }
#notifications {
background: transparent; }
.notifications {
opacity: 1;
min-width: 24rem; }
.notifications .revealer + .revealer > * > box {
margin-top: 0; }
.notifications .revealer .notification {
margin: 25px;
min-width: 24rem;
padding: 0px;
border: 2px solid #f2f4f8;
background: #161616; }
.notifications .revealer .notification.critical {
border: 2px solid #FF7EB6; }
.notifications .revealer .body {
margin-right: 1em; }
.notifications .revealer .timeout-bar {
margin: 5px 0px 0;
margin-top: 5px;
background: #262626; }
.notifications .revealer .timeout-bar > trough > progress {
background-image: none;
background-color: #33B1FF; }
.notifications .revealer .button {
font-size: 20px;
margin-left: 5px;
margin-right: 5px; }
.notifications .revealer .button:hover {
color: #FF7EB6; }
.notifications .revealer .icon {
min-width: 68px;
min-height: 68px;
margin-right: 1em;
margin-left: 1em; }
.notifications .revealer .icon image {
font-size: 58px;
margin: 5px;
color: #f2f4f8; }
.notifications .revealer .icon box {
min-width: 68px;
min-height: 68px; }
.notifications .revealer .title {
font-size: 24px; }
.notifications .revealer .actions .action-button {
margin: 0 .4em;
margin-top: .8em;
border: 2px solid #f2f4f8; }
.notifications .revealer .actions .action-button:first-child {
margin-left: .5em; }
.notifications .revealer .actions .action-button:last-child {
margin-right: .5em; }
.launcher {
min-width: 640px;
background: #262626; }
.launcher .search > image {
margin-left: 10px; }
.launcher .search > entry {
padding: 5px;
margin: 10px;
background: #161616;
border: 5px solid #161616;
border-radius: 10px;
font-size: 32px; }
.launcher .entry {
margin: 0 10px 0 10px;
border: 10px solid #161616;
border-radius: 10px;
background: #161616; }
.launcher .entry > box > image {
padding: 5px;
margin: 5px 10px 5px 10px;
background: #262626;
border-radius: 5px; }
.launcher > scrolledwindow > * > box:last-child {
margin-bottom: 10px; }
* :not(selection) :not(tooltip) {
all: unset; }

View File

@ -1,27 +0,0 @@
@mixin dial($_fg, $_bg, $margin, $padding, $font-size, $icon-font-size) {
color: $_fg;
background-color: $_bg;
margin: $margin;
padding: $padding;
font-size: $font-size;
.dial-icon {
font-family: 'Symbols Nerd Font Mono';
font-size: $icon-font-size;
color: $icon-color;
}
}
@mixin bar-dial($_fg, $_bg) {
@include dial($_fg, $_bg, 0px, 0px, 5px, 16px)
}
@mixin panel-dial($_fg, $_bg) {
@include dial($_fg, $_bg, 0px, 10px, 8px, 32px);
margin-left: 10px;
border-radius: 10px;
.resource-dial {
min-width: 96px;
min-height: 96px;
}
}

View File

@ -1,320 +1,19 @@
.lock-ready > box box {
* {
all: unset; }
bar {
background: transparent; }
box.workspaces {
min-height: 50px;
background: #161616;
padding: 20px;
border: 10px solid #33B1FF;
min-width: 300px;
margin: 15px; }
.lock-ready > box .img {
background: url("/home/catalie/.config/wallpaper");
min-width: 300px;
min-height: 300px;
background-position: 25% 12.5%;
background-size: cover; }
.lock-ready > box box > entry {
background: #262626;
font-size: 14px;
padding: 5px;
margin: 10px; }
.lock {
background: transparent; }
* :not(selection) :not(tooltip) {
all: unset; }
#status-bar {
background-color: #161616;
border-radius: 0 10px 10px 0; }
#status-bar .dial-container {
margin-top: 10px; }
#status-bar .dial-container .battery-dial {
color: #42be65;
background-color: #161616;
margin: 0px;
padding: 0px;
font-size: 5px; }
#status-bar .dial-container .battery-dial .dial-icon {
font-family: 'Symbols Nerd Font Mono';
font-size: 16px;
color: #f2f4f8; }
#status-bar .dial-container .volume-dial {
color: #33B1FF;
background-color: #161616;
margin: 0px;
padding: 0px;
font-size: 5px; }
#status-bar .dial-container .volume-dial .dial-icon {
font-family: 'Symbols Nerd Font Mono';
font-size: 16px;
color: #f2f4f8; }
#status-bar .dial-container .brightness-dial {
color: #33B1FF;
background-color: #161616;
margin: 0px;
padding: 0px;
font-size: 5px; }
#status-bar .dial-container .brightness-dial .dial-icon {
font-family: 'Symbols Nerd Font Mono';
font-size: 16px;
color: #f2f4f8; }
#status-bar .workspace-container {
background-color: #262626;
border-radius: 10px;
padding: 6px 0px;
margin: 0px 12px;
border: none; }
#status-bar .workspace-container .ws-norm {
min-width: 15px;
border-radius: 10px;
margin: 2px 5px;
padding: 6px 10px;
background-color: #161616;
color: #f2f4f8; }
#status-bar .workspace-container .ws-active {
color: #161616;
background-color: #FF7EB6; }
#status-bar .menu-visibility-button {
border-radius: 0px 10px 10px 0px;
padding: 10px 0px 10px 2px;
background-color: #262626; }
#status-bar .clock {
background-color: #262626;
margin: 8px;
margin-bottom: 10px;
padding: 6px 2px;
border-radius: 10px; }
#status-bar .mpd-controls .button {
font-size: 14px;
margin: 2px;
margin-bottom: 8px;
font-family: 'Symbols Nerd Font Mono'; }
#status-bar .mpd-controls .button:hover {
color: #FF7EB6; }
#status-bar {
background: transparent; }
#status-bar .workspaces {
min-height: 50px;
background: #161616;
padding: 5px;
padding-top: 15px; }
#status-bar .workspaces .workspace {
min-width: 10px;
min-height: 10px;
margin: 5px;
background-color: #262626; }
#status-bar .workspaces .occupied {
background-color: #393939; }
#status-bar .workspaces .focused {
background-color: #FF7EB6; }
#status-bar .media-controls {
margin-top: 20px;
min-width: 220px; }
#status-bar .media-controls .button {
font-size: 20px; }
#status-bar .progress-bar {
margin-top: 20px;
min-width: 200px;
min-height: 2px;
background: transparent; }
#status-bar .progress-bar trough {
min-height: 2px;
background: #262626; }
#status-bar .progress-bar highlight {
min-height: 2px;
background: #FF7Eb6; }
#status-bar .right {
min-height: 50px;
background: #161616; }
#status-bar .right .battery-container {
padding-left: 10px;
padding-right: 10px; }
#status-bar .right .battery-container .battery-dial {
color: #42be65;
background-color: #161616;
margin: 0px;
padding: 0px;
font-size: 5px; }
#status-bar .right .battery-container .battery-dial .dial-icon {
font-family: 'Symbols Nerd Font Mono';
font-size: 16px;
color: #f2f4f8; }
#status-bar .right .sliderbox {
margin: 5px; }
#status-bar .right .sliderbox .volume .slider trough {
min-height: 10px;
min-width: 120px;
background: #262626; }
#status-bar .right .sliderbox .volume .slider highlight {
background: #33B1FF; }
#status-bar .right .sliderbox .volume button {
font-size: 20px;
margin: 5px; }
#status-bar .right .sliderbox .brightness .slider trough {
min-height: 10px;
min-width: 120px;
background: #262626; }
#status-bar .right .sliderbox .brightness .slider highlight {
background: #33B1FF; }
#status-bar .right .sliderbox .brightness button {
font-size: 20px;
margin: 5px; }
#status-bar .right separator {
background: #262626;
padding: 1px;
margin-top: 10px;
margin-bottom: 10px; }
#status-bar .right .clock {
background: #161616; }
#status-bar .right .clock .datetime {
margin: 1px;
font-size: 10pt; }
#status-bar .Menu {
background-color: #262626;
min-width: 640px; }
#status-bar .Menu .mpd-controls {
background-color: #161616;
border-radius: 10px;
margin: 10px; }
#status-bar .Menu .mpd-controls .button {
font-size: 24px;
padding: 10px;
margin: 10px;
background-color: #262626;
border-color: #262626;
border-radius: 10px; }
#status-bar .Menu .mpd-controls .cover {
min-width: 250px;
min-height: 250px;
border-radius: 10px;
margin: 10px;
background: #161616;
background-size: cover; }
#status-bar .Menu .mpd-controls .title {
padding-top: 10px;
font-size: 20px;
font-weight: bold; }
#status-bar .Menu .mpd-controls .position {
margin: 10px;
margin-right: 20px;
min-height: 10px;
border-radius: 5px; }
#status-bar .Menu .mpd-controls .position trough {
background: #262626;
border-radius: 5px;
min-height: 10px; }
#status-bar .Menu .mpd-controls .position slider {
min-height: 10px;
border-radius: 5px;
background: #FF7Eb6; }
#status-bar .Menu .mpd-controls .position-label {
margin-top: 75px; }
#status-bar .Menu .dial-parent {
color: #FF7eb6;
background-color: #161616;
margin: 0px;
padding: 10px;
font-size: 8px;
margin-left: 10px;
border-radius: 10px;
border-radius: 10px; }
#status-bar .Menu .dial-parent .dial-icon {
font-family: 'Symbols Nerd Font Mono';
font-size: 32px;
color: #f2f4f8; }
#status-bar .Menu .dial-parent .resource-dial {
min-width: 96px;
min-height: 96px; }
#status-bar .Menu .dial-parent .dial-icon {
color: #f2f4f8; }
#notifications {
background: transparent; }
.notifications {
opacity: 1;
min-width: 24rem; }
.notifications .revealer + .revealer > * > box {
margin-top: 0; }
.notifications .revealer .notification {
margin-bottom: 25px;
min-width: 24rem;
padding: 0px;
border: 1px solid #f2f4f8;
border-right: none;
background: #161616; }
.notifications .revealer .urgency-indicator {
min-width: 15px;
margin-bottom: 25px; }
.notifications .revealer .urgency-indicator.normal, .notifications .revealer .urgency-indicator.low {
background: #42be65;
border: 1px solid #42be65; }
.notifications .revealer .urgency-indicator.urgent {
background: #FF7EB6;
border: 1px solid #FF7EB6; }
.notifications .revealer .body {
margin-right: 1em; }
.notifications .revealer .timeout-bar {
background: #262626; }
.notifications .revealer .timeout-bar > trough > progress {
background-image: none;
background-color: #33B1FF; }
.notifications .revealer .button {
font-size: 20px;
margin-left: 5px;
margin-right: 5px; }
.notifications .revealer .button:hover {
color: #FF7EB6; }
.notifications .revealer .icon {
min-width: 68px;
min-height: 68px;
margin: 5px; }
.notifications .revealer .icon image {
font-size: 58px;
padding: 5px; }
box.workspaces box > button {
min-width: 10px;
min-height: 10px;
margin: 5px;
color: #f2f4f8; }
.notifications .revealer .icon box {
min-width: 68px;
min-height: 68px; }
.notifications .revealer .title {
font-size: 28px;
padding-right: 5px; }
.notifications .revealer .actions .action-button {
margin: 0 .4em;
margin-top: .8em;
border: 2px solid #f2f4f8; }
.notifications .revealer .actions .action-button:first-child {
margin-left: .5em; }
.notifications .revealer .actions .action-button:last-child {
margin-right: .5em; }
.launcher {
min-width: 640px;
background: #262626; }
.launcher .search > image {
margin-left: 10px; }
.launcher .search > entry {
padding: 5px;
margin: 10px;
background: #161616;
border: 5px solid #161616;
border-radius: 10px;
font-size: 32px; }
.launcher .entry {
margin: 0 10px 0 10px;
border: 10px solid #161616;
border-radius: 10px;
background: #161616; }
.launcher .entry > box > image {
padding: 5px;
margin: 5px 10px 5px 10px;
background: #262626;
border-radius: 5px; }
.launcher > scrolledwindow > * > box:last-child {
margin-bottom: 10px; }
* :not(selection) :not(tooltip) {
all: unset; }
background-color: #262626; }
box.workspaces box > button.occupied {
background-color: #393939; }
box.workspaces box > button.focused {
background-color: #FF7EB6; }

View File

@ -1,14 +1,7 @@
@import 'colors.scss';
@import 'mixins.scss';
// components
@import './components/lock.scss';
@import './components/bar.scss';
@import './components/top-bar.scss';
@import './components/bar-control-center.scss';
@import './components/notification-popups.scss';
@import './components/launcher.scss';
* :not(selection) :not(tooltip) {
all: unset;
* {
all: unset;
}
@import 'colors.scss';
@import './widgets/bar.scss'

26
style/widgets/bar.scss Normal file
View File

@ -0,0 +1,26 @@
$height: 50px;
bar {
background: transparent
}
box.workspaces {
min-height: $height;
background: $bg;
padding: 5px;
box > button {
min-width: 10px;
min-height: 10px;
margin: 5px;
background-color: $bg-alt-1;
&.occupied {
background-color: $bg-alt-2;
}
&.focused {
background-color: $hl;
}
}
}

View File

@ -1,29 +0,0 @@
export default ({
name,
child,
transition = "slide_up",
transitionDuration = 250,
...props
}) => {
const reveal = Variable(false)
const window = Widget.Window({
name,
visible: false,
...props,
child: Widget.Box({
css: `min-height: 2px;
min-width: 2px;`,
child: Widget.Revealer({
transition,
transitionDuration,
hexpand: true,
vexpand: true,
child: child,
revealChild: reveal.bind()
}),
}),
});
return window, reveal;
}

View File

@ -1,38 +0,0 @@
const battery = await Service.import('battery')
const battery_dial = Widget.CircularProgress({
className: 'battery-dial',
rounded: false,
inverted: false,
startAt: 0.75,
value: battery.bind('percent').as(p => p / 100),
child: Widget.Label({
className: "dial-icon",
hexpand: true,
setup: (self) => {
self.hook(battery, (self) => {
console.log(battery)
const icons = [
["󰂎", "󰁺", "󰁻", "󰁼", "󰁽", "󰁾", "󰁿", "󰂀", "󰂁", "󰂂", "󰁹"],
["󰢟", "󰢜", "󰂆", "󰂇", "󰂈", "󰢝", "󰂉", "󰢞", "󰂊", "󰂋", "󰂅"],
];
self.label = icons[Number(battery.charging)][Math.floor(battery.percent / 10)];
self.tooltip_text = 'Battery ' + String(battery.percent) + '%';
});
}
}),
setup: (self) => {
self.hook(battery, (self) => {
if (battery.percent <= 30 && battery.charging === false) {
self.toggleClassName("battery-low", true);
} else {
self.toggleClassName("battery-low", false);
}
});
}
});
export {
battery_dial
}

View File

@ -1,63 +0,0 @@
const { exec, execAsync } = Utils;
import Brightness from '../services/brightness.js'
const brightness_dial = Widget.EventBox({
className: 'eventbox-hide-pointer',
'on-primary-click': () => {execAsync('hyprshade toggle blue-light-filter')},
'on-scroll-up': () => {Brightness.screen += 0.01},
'on-scroll-down': () => {Brightness.screen -= 0.01},
child: Widget.CircularProgress({
rounded: false,
className: 'brightness-dial',
inverted: false,
startAt: 0.75,
value: Brightness.bind('screen'),
child: Widget.Label({
className: "dial-icon",
hexpand: true,
hpack: 'center',
setup: (self) => {
self.hook(Brightness, (self => {
const brightness = Brightness.screen * 100;
self.label = ["󰃚", "󰃛", "󰃜", "󰃝", "󰃞", "󰃟", "󰃠"][Math.floor(brightness/15)]
self.tooltip_text = `Brightness ${Math.floor(brightness)}%`;
}))
}
})
})
})
const brightness_slider = Widget.Box({
className: 'brightness',
children: [
Widget.Button({
on_clicked: () => execAsync('hyprshade toggle blue-light-filter'),
child: Widget.Icon().hook(Brightness, self => {
const brightness = Brightness.screen * 100;
const icon = [
[80, 'display-brightness-high-symbolic'],
[50, 'display-brightness-medium-symbolic'],
[20, 'display-brightness-low-symbolic'],
[0, 'display-brightness-off-symbolic']
].find(([threshold]) => brightness >= threshold)?.[1];
self.icon = icon;
self.tooltip_text = `Brightness ${Math.floor(brightness)}%`;
}),
}),
Widget.Slider({
className: 'slider',
hexpand: true,
drawValue: false,
onChange: ({ value }) => Brightness.screen = value,
value: Brightness.bind('screen'),
})
]
});
export {
brightness_dial,
brightness_slider
}

View File

@ -1,62 +0,0 @@
const { exec, execAsync } = Utils;
const bar_clock = Widget.Box({
className: 'clock',
vpack: 'end',
vertical: true,
setup: (self) => {
var month_and_date, hours_and_minutes, seconds;
self.poll(1000, self => {
execAsync("date +'%m/%d %H:%M %S'").then((time) => {
[month_and_date, hours_and_minutes, seconds] = time.split(' ');
});
self.children = [
Widget.Label({
className: 'datetime',
label: month_and_date
}),
Widget.Label({
className: 'datetime',
label: hours_and_minutes
}),
Widget.Label({
className: 'datetime',
label: seconds
}),
]
})
}
})
const horizontal_clock = Widget.Box({
className: 'clock',
vpack: 'center',
vertical: true,
setup: (self) => {
var date_time, unix_seconds;
self.poll(1000, self => {
execAsync("date +'%d %b %H:%M:%S %s'").then((time) => {
let parts = time.split(' ');
date_time = `${parts[0]} ${parts[1]} ${parts[2]}`;
unix_seconds = parts[3];
});
self.children = [
Widget.Label({
className: 'datetime',
label: date_time
}),
Widget.Label({
hpack: 'start',
className: 'datetime',
label: unix_seconds
})
];
});
}
});
export {
bar_clock,
horizontal_clock
}

View File

@ -1,10 +0,0 @@
const hyprland = await Service.import('hyprland')
const active_window = Widget.Label({
className: 'active-window',
label: '',//hyprland.bind('active').as(c => c.client.title),
})
export {
active_window
}

View File

@ -1,59 +0,0 @@
const hyprland = await Service.import('hyprland')
const goto_workspace = (ws) => hyprland.messageAsync(`dispatch workspace ${ws}`)
const hyprworkspaces = Widget.EventBox({
onScrollUp: () => goto_workspace('+1'),
onScrollDown: () => goto_workspace('-1'),
child: Widget.Box({
vertical: true,
className: 'workspace-container',
children: Array.from({ length: 10 }, (_, i) => i + 1).map(i => Widget.Button({
className: 'ws-norm',
attribute: i,
child: Widget.Label(String(i)),
onClicked: () => goto_workspace(i),
setup: self => self.hook(hyprland, self => self.attribute == hyprland.active.workspace.id ?
self.toggleClassName('ws-active', true)
: self.toggleClassName('ws-active', false))
})),
setup: self => self.hook(hyprland, () => self.children.forEach(btn => {
btn.visible = hyprland.workspaces.some(ws => ws.id === btn.attribute);
})),
}),
})
function workspace_row(start, length) {
return Widget.Box({
vpack: 'center',
hpack: 'center',
className: 'workspace-row',
children: Array.from({ length: length }, (_, i) => i + 1 + start).map(i => Widget.Button({
className: 'workspace',
attribute: i,
onClicked: () => goto_workspace(i),
setup: self => {
self.hook(hyprland, self => {
self.attribute == hyprland.active.workspace.id ? self.toggleClassName('focused', true) : self.toggleClassName('focused', false)
hyprland.workspaces.map(w => w.id).includes(self.attribute) ? self.toggleClassName('occupied', true) : self.toggleClassName('occupied', false)
})
}
}))
})
}
const hyprworkspaces_grid = Widget.Box({
className: 'workspaces',
vertical: true,
children: [
workspace_row(0, 5),
workspace_row(5, 5)
]
})
export {
hyprworkspaces,
hyprworkspaces_grid
}

View File

@ -1,87 +0,0 @@
const { Gtk, cairo } = imports.gi;
const register = Widget.register;
class MarqueeLabel extends Gtk.DrawingArea {
static {
register(this, {
properties: {
label: ["string", "rw"],
"scroll-speed": ["int", "rw"],
},
});
}
#xOffset;
#scrollDirection;
get label() {
return this._label;
}
set label(label) {
this._label = label;
this.queue_draw();
this.notify("label");
}
get scroll_speed() {
return this._scrollSpeed;
}
set scroll_speed(speed) {
this._scrollSpeed = speed;
this.notify("scroll-speed");
}
constructor(props) {
super(props);
this._reset();
this.poll(this._scrollSpeed * 11, () => this.queue_draw());
this.on("size-allocate", () => this._reset());
this.on("notify::label", () => this._reset());
}
_reset() {
this.#xOffset = 1;
this.#scrollDirection = 0;
}
vfunc_draw(cr) {
const allocation = this.get_allocation();
const styles = this.get_style_context();
const width = allocation.width;
const height = allocation.height;
const color = styles.get_color(Gtk.StateFlags.NORMAL);
const [fontFamily] = styles.get_property(
"font-family",
Gtk.StateFlags.NORMAL,
);
const fontSize = Math.floor(
styles.get_property("font-size", Gtk.StateFlags.NORMAL),
);
cr.setSourceRGB(color.red, color.green, color.blue);
cr.selectFontFace(fontFamily, null, null);
cr.setFontSize(fontSize);
const labelWidth = cr.textExtents(this._label).width;
if (labelWidth > width) {
this.#xOffset += this.#scrollDirection * this._scrollSpeed;
if (this.#xOffset >= 1 || this.#xOffset <= width - labelWidth) {
this.#scrollDirection *= 0;
}
} else {
this.#xOffset = (width - labelWidth) / 3;
}
cr.moveTo(this.#xOffset*1.475, fontSize);
cr.showText(this._label);
}
}
export default MarqueeLabel;

View File

@ -1,182 +0,0 @@
const { Gtk } = imports.gi;
import Mpd from '../services/mpd.js';
import MarqueeLabel from './marqueeLabel.js'
const Mpris = await Service.import("mpris");
const AspectFrame = Widget.subclass(Gtk.AspectFrame);
function lengthString(length) {
return (
`${Math.floor(length / 60)
.toString()
.padStart(2, "0")}:` +
`${Math.floor(length % 60)
.toString()
.padStart(2, "0")}`
);
}
const albumCover = Widget.Box({
className: "cover",
setup: (self) => self.hook(Mpris, () => {
const mpd = Mpris.getPlayer("mpd");
self.css = `background-image: url("${mpd?.coverPath}");`;
}),
});
const positionLabel = Widget.Label({
className: 'position-label',
setup: (self) => self.poll(500, () => {
Mpd.send("status")
.then((msg) => {
const elapsed = msg?.match(/elapsed: (\d+\.\d+)/)?.[1];
self.label = `${lengthString(elapsed || 0)} / ${lengthString(Mpd.duration || 0)}`;
})
.catch((error) => logError(error));
}),
});
const positionSlider = Widget.Slider({
className: 'position',
vpack: 'end',
drawValue: false,
onChange: ({ value }) => {
Mpd.seekCur(value * Mpd.duration);
},
setup: (self) => {
self.poll(500, () => {
Mpd.send("status")
.then((msg) => {
const elapsed = msg?.match(/elapsed: (\d+\.\d+)/)?.[1];
self.value = elapsed / Mpd.duration || 0;
})
.catch((error) => logError(error));
});
},
});
const songTitle = Widget.Box({
className: 'title',
children: [
new MarqueeLabel({
heightRequest: 30,
widthRequest: 350,
scrollSpeed: 1,
label: 'No Title',
setup: (self) => {
self.hook(Mpd, () => {
self.label = `${Mpd.Title || "No Title"}`;
});
},
})
]
})
const songArtist = Widget.Box({
className: 'artist',
children: [
new MarqueeLabel({
heightRequest: 30,
widthRequest: 350,
scrollSpeed: 1,
label: 'No Artist',
setup: (self) => {
self.hook(Mpd, () => {
self.label = `${Mpd.Artist || "No Artist"}`;
});
},
})
]
})
const mediaControls = Widget.Box()
const mpd_controls = () => Widget.CenterBox({
className: 'mpd-controls',
hpack: 'center',
hexpand: true,
startWidget: Widget.Button({
hpack: 'start',
className: 'button',
onClicked: () => Mpd.previous(),
child: Widget.Icon('media-skip-backward-symbolic')
}),
centerWidget: Widget.Button({
hpack: 'center',
className: 'button',
onClicked: () => Mpd.playPause(),
child: Widget.Icon({
setup: self => self.hook(Mpd, () => (Mpd.state === 'play')
? self.icon = 'media-playback-pause-symbolic'
: self.icon = 'media-playback-start-symbolic')
})
}),
endWidget: Widget.Button({
hpack: 'end',
className: 'button',
onClicked: () => Mpd.next(),
child: Widget.Icon('media-skip-forward-symbolic')
})
})
export const mpd_bar_controls = mpd_controls()
export const mpd_menu_controls = Widget.Box({
className: 'mpd-controls',
children: [
albumCover,
Widget.Box({
vertical: true,
hpack: 'end',
children: [
Widget.Box({
vertical: true,
children: [
songTitle,
songArtist
]
}),
mpd_controls(),
positionLabel,
positionSlider
]
})
]
})
export const cover_with_controls = Widget.Box({
className: 'cover',
hexpand: true,
vexpand: true,
child: Widget.Box({
className: "cover",
vertical: true,
children: [
mpd_controls(),
Widget.Slider({
vpack: 'end',
className: 'progress-bar',
drawValue: false,
hexpand: false,
onChange: ({ value }) => Mpd.seekCur(value * Mpd.duration),
setup: (self) => {
self.poll(500, () => {
Mpd.send("status")
.then((msg) => {
const elapsed = msg?.match(/elapsed: (\d+\.\d+)/)?.[1];
self.value = elapsed / Mpd.duration || 0;
})
.catch((error) => logError(error));
});
},
})
],
setup: (self) =>
self.hook(Mpris, () => {
const mpd = Mpris.getPlayer("mpd");
self.css = `background: linear-gradient(rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.6)), url("${mpd?.coverPath}"); min-height: 60px; min-width: 250px; background-position: 50% 50%; background-size: cover; border: 5px solid #161616`;
})
})
})

View File

@ -1,114 +0,0 @@
const { Gtk, Gdk } = imports.gi;
const Mpris = await Service.import('mpris')
const media_controls = (player) => Widget.CenterBox({
className: 'media-controls',
hpack: 'center',
hexpand: true,
startWidget: Widget.Button({
hpack: 'start',
className: 'button',
onClicked: () => player.previous(),
child: Widget.Icon('media-skip-backward-symbolic')
}),
centerWidget: Widget.Button({
hpack: 'center',
className: 'button',
onClicked: () => player.playPause(),
child: Widget.Icon({
setup: self => self.hook(Mpris, () => (player.playBackStatus === 'Playing')
? self.icon = 'media-playback-pause-symbolic'
: self.icon = 'media-playback-start-symbolic')
})
}),
endWidget: Widget.Button({
hpack: 'end',
className: 'button',
onClicked: () => player.next(),
child: Widget.Icon('media-skip-forward-symbolic')
})
})
const cover_with_controls = (player) => Widget.Box({
className: "media",
children: [
Widget.Box({
vertical: true,
children: [
media_controls(player),
Widget.Slider({
vpack: 'end',
className: 'progress-bar',
drawValue: false,
hexpand: false,
onChange: ({ value }) => {player.position = (value * player.length)},
setup: self => self.poll(500, self => {
if (!player) return
self.value = player.position/player.length
})
})
],
}),
],
setup: (self) => {
self.hook(Mpris, () => {
self.queue_draw()
self.css = `background: linear-gradient(rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.6)), url("${player.coverPath}"); min-height: 60px; min-width: 250px; background-position: 50% 50%; background-size: cover; border: 5px solid #161616`;
})
},
});
export const players = Widget.Stack({
transition: "slide_up_down",
transitionDuration: 125,
children: {},
setup: (self) => {
self.add_events(Gdk.EventMask.SCROLL_MASK);
self.add_events(Gdk.EventMask.SMOOTH_SCROLL_MASK);
let currentDeltaY = 0;
self.on("scroll-event", (_, event) => {
const childNames = Object.keys(self.children);
const length = Object.keys(self.children).length
const prevChild = childNames[((n)=>n>=0?n:length-1)((childNames.indexOf(self.get_visible_child_name()) - 1) % length)];
const nextChild = childNames[(childNames.indexOf(self.get_visible_child_name()) + 1) % length];
const deltaY = event.get_scroll_deltas()[2];
currentDeltaY += deltaY;
if (currentDeltaY > 10 && prevChild) {
self.set_visible_child_name(prevChild);
currentDeltaY = 0;
}
if (currentDeltaY < -10 && nextChild) {
self.set_visible_child_name(nextChild);
currentDeltaY = 0;
}
console.log(self.children)
});
self.hook(Mpris, (_, name) => {
if (!name) return;
self.add_named(cover_with_controls(Mpris.getPlayer(name)), name);
}, "player-added");
self.hook(Mpris, (_, name) => {
if (!name) return;
self.get_child_by_name(name).destroy();
delete children[name]
console.log('destroyed')
}, "player-closed");
},
});
export const media = Widget.Revealer({
revealChild: Mpris.bind("players").as((players) => players.length > 0),
transition: "slide_up",
transitionDuration: 125,
child: players,
});

View File

@ -1,24 +0,0 @@
const { Gio, GioUnix } = imports.gi;
export const resource_dial = (label, icon, timeout, command, transformer) => {
let poll = Variable(100, {
poll: [timeout, command, v => Number(v)]
})
return Widget.Box({
className: 'dial-parent',
children: [
Widget.CircularProgress({
startAt: 0.75,
className: 'resource-dial',
value: poll.bind().as(v => transformer(v)),
tooltipText: poll.bind().as(v => label(v)),
child: Widget.Icon({
className: 'dial-icon',
icon: icon
}),
})
]
})
}

View File

@ -1,14 +0,0 @@
const systemtray = await Service.import('systemtray')
const SysTrayItem = item => Widget.Button({
child: Widget.Icon().bind('icon', item, 'icon'),
tooltipMarkup: item.bind('tooltip_markup'),
onPrimaryClick: (_, event) => item.activate(event),
onSecondaryClick: (_, event) => item.openMenu(event),
});
export const systray = Widget.Box({
className: 'systray',
vertical: true,
children: systemtray.bind('items').as(i => i.map(SysTrayItem))
})

View File

@ -1,70 +0,0 @@
const { exec, execAsync } = Utils
const audio = await Service.import('audio')
const volume_dial = Widget.EventBox({
className: 'eventbox-hide-pointer',
'on-scroll-up': () => {audio.speaker.volume += 0.01},
'on-scroll-down': () => {audio.speaker.volume -= 0.01},
'on-primary-click': () => {audio.speaker.is_muted = !audio.speaker.is_muted},
child: Widget.CircularProgress({
className: 'volume-dial',
rounded: false,
inverted: false,
startAt: 0.75,
value: audio.speaker.bind('volume'),
child: Widget.Icon({
className: "dial-icon",
hexpand: true,
setup: (self) => {
self.hook(audio, (self => {
const vol = audio.speaker.volume * 100;
const icon = [
[101, 'overamplified'],
[67, 'high'],
[34, 'medium'],
[1, 'low'],
[0, 'muted'],
].find(([threshold]) => threshold <= vol)?.[1];
self.icon = `audio-volume-${icon}-symbolic`;
self.tooltip_text = `Volume ${Math.floor(vol)}%`;
}))
}
})
})
})
const volume_slider = Widget.Box({
className: 'volume',
children: [
Widget.Button({
on_clicked: () => audio.speaker.is_muted = !audio.speaker.is_muted,
child: Widget.Icon().hook(audio.speaker, self => {
const vol = audio.speaker.volume * 100;
const icon = [
[101, 'overamplified'],
[67, 'high'],
[34, 'medium'],
[1, 'low'],
[0, 'muted'],
].find(([threshold]) => threshold <= vol)?.[1];
self.icon = `audio-volume-${icon}-symbolic`;
self.tooltip_text = `Volume ${Math.floor(vol)}%`;
}),
}),
Widget.Slider({
className: 'slider',
hexpand: true,
drawValue: false,
onChange: ({ value }) => audio['speaker'].volume = value,
value: audio['speaker'].bind('volume'),
})
]
})
export {
volume_dial,
volume_slider
}

View File

@ -1,141 +0,0 @@
import { battery_dial } from '../widgets/battery.js'
import { volume_dial } from '../widgets/volume.js'
import { brightness_dial } from '../widgets/brightness.js'
import { hyprworkspaces } from '../widgets/hyprworkspaces.js'
import { mpd_bar_controls } from '../widgets/mpd.js'
// import { systray } from '../widgets/systray.js'
import { bar_clock} from '../widgets/clock.js'
import { MenuWidget } from './menu.js'
import { reveal_launcher, launcher } from './launcher.js'
export const FakeBar = Widget.Window({
name: 'FakeBar',
exclusivity: 'exclusive',
anchor: ['left'],
margins: [0, 35],
child: Widget.Box({css: 'min-width: 1px;'})
})
let reveal_menu = Variable(false)
let reveal_menu_button = Variable(false)
const MenuRevealButton = Widget.Box({
children: [
Widget.Button({
vexpand: false,
vpack: 'center',
className: 'menu-visibility-button',
'on-primary-click': (self) => reveal_menu.value = !reveal_menu.value,
child: Widget.Icon({
icon: reveal_menu.bind().as(reveal => !reveal ? 'go-next-symbolic' : 'go-previous-symbolic')
})
})
]
})
const BarTopWidget = Widget.Box({
vertical: true,
vpack: 'start',
children: [
Widget.Box({
className: 'dial-container',
vertical: true,
spacing: 8,
children: [
battery_dial,
volume_dial,
brightness_dial
]
}),
Widget.Box({
css: 'margin-left: 35px; margin-right: 35px'
})
]
})
const BarMiddleWidget = Widget.Box({
children: [
Widget.EventBox({
'on-hover': () => reveal_menu_button.value = true,
child: Widget.Box({
css: 'min-width: 1px; min-height: 40px;'
})
}),
hyprworkspaces
]
})
const BarEndWidget = Widget.Box({
vertical: true,
vpack: 'end',
children: [
// systray,
mpd_bar_controls,
bar_clock
]
})
const BarWidget = Widget.CenterBox({
homogeneous: false,
vertical: true,
spacing: 10,
startWidget: BarTopWidget,
centerWidget: BarMiddleWidget,
endWidget: BarEndWidget
})
const MenuButtonRevealer = Widget.Revealer({
revealChild: reveal_menu_button.bind(),
transition: 'slide_right',
child: MenuRevealButton
})
const MenuRevealer = Widget.Revealer({
revealChild: reveal_menu.bind(),
transition: 'slide_right',
transitionDuration: 500,
child: MenuWidget,
})
const LauncherRevealer = Widget.Revealer({
revealChild: reveal_launcher.bind(),
transition: 'slide_right',
transitionDuration: 500,
child: launcher,
})
export const Bar = Widget.Window({
name: 'status-bar',
exclusivity: 'ignore',
keymode: reveal_launcher.bind().as(v => v ? 'exclusive' : 'on-demand'),
anchor: ['top', 'left', 'bottom'],
child: Widget.Overlay({
'pass-through': true,
overlay: Widget.Box({
children: [
Widget.EventBox({
'on-hover': () => reveal_menu_button.value = true,
'on-hover-lost': () => reveal_menu_button.value = false,
child: Widget.Box({
css: 'margin-right: 4px;',
children: [MenuButtonRevealer]
})
}),
]
}),
child: Widget.Box({
children: [
LauncherRevealer,
MenuRevealer,
BarWidget
]
})
})
})
export {
reveal_menu,
reveal_launcher
}

View File

@ -1,93 +0,0 @@
const Applications = await Service.import("applications")
const reveal_launcher = Variable(false)
function appItem(app) {
return Widget.Button({
className: 'entry',
onClicked: () => {
reveal_launcher.value = false
app.launch()
},
attribute: { app },
child: Widget.Box([
Widget.Icon({
icon: app.icon_name || '',
size: 42,
}),
Widget.Label({
className: 'app-title',
label: app.name,
xalign: 0,
vpack: 'center',
truncate: 'end'
})
])
})
}
function _launcher() {
let applications = Applications.query('').map(appItem)
const list = Widget.Box({
vertical: true,
children: applications,
spacing: 12
})
function repopulate() {
applications = Applications.query('').map(appItem)
list.children = applications
}
const entry = Widget.Box({
className: 'search',
children: [
Widget.Icon({
icon: 'edit-find-symbolic',
size: 42,
}),
Widget.Entry({
hexpand: true,
className: 'search',
on_accept: () => {
applications.filter((item) => item.visible)[0]?.attribute.app.launch()
reveal_launcher.value = false
},
on_change: ({ text }) => applications.forEach(item => {
item.visible = item.attribute.app.match(text ?? '')
})
})
]
})
return Widget.Box({
vertical: true,
className: 'launcher',
children: [
entry,
Widget.Scrollable({
hscroll: 'never',
vexpand: true,
hexpand: true,
child: list
})
],
setup: self => self.hook(reveal_launcher, () => {
entry.text = ''
if (reveal_launcher.value) {
repopulate()
console.log('nya')
entry.text = ''
entry.grab_focus()
}
}, "changed")
})
}
const launcher = _launcher()
export {
reveal_launcher,
launcher
}

View File

@ -1,76 +0,0 @@
import { show_notification_popups } from './notifications.js'
// TODO: const { Gdk, GtkSessionLock } = imports.gi;
const { authenticateUser, exec } = Utils
export const show_lock = Variable(false)
const lock_ready = Variable(false)
const username = Variable()
const password_entry = Widget.Entry({
visibility: false,
xalign: 0.5,
onAccept: (self) => {
authenticateUser(username.value, self.text)
.then(() => {
show_lock.value = false
exec('rm /tmp/lock-pixelated.png')
show_notification_popups.value = true
})
.catch(() => self.text = '')
self.text = ''
username_entry.text = ''
username_entry.grab_focus()
},
setup: self => self.text = ''
})
const username_entry = Widget.Entry({
xalign: 0.5,
onAccept: (self) => {
username.value = self.text
password_entry.grab_focus()
},
})
const login_container = Widget.Box({
child: Widget.Box({
className: lock_ready.bind().as(b => b ? 'lock-ready' : 'lock'),
vpack: 'center',
hpack: 'center',
hexpand: 'true',
vertical: true,
child: Widget.Box({
vertical: true,
children: [
Widget.Box({className: 'img'}),
Widget.Box({
vertical: true,
children: [
username_entry,
password_entry
]
})
]
})
}),
setup: self => self.hook(show_lock, () => {
self.css = 'min-width: 2560px; min-height: 1600px; background: url("/tmp/lock-pixelated.png");'
lock_ready.value = true
})
})
export const Lock = Widget.Window({
name: 'lock',
visible: show_lock.bind().as(b => {
if (b) {
exec(`bash -c "grim - | convert - -scale 12.5% -scale 800% -filter point /tmp/lock-pixelated.png"`)
show_notification_popups.value = false
}
return b
}),
exclusivity: 'ignore',
keymode: 'exclusive',
child: login_container,
})

View File

@ -1,49 +0,0 @@
import { mpd_menu_controls } from '../widgets/mpd.js'
import { resource_dial } from '../widgets/resourceDial.js'
export const MenuWidget = Widget.Box({
vertical: true,
className: 'Menu',
children: [
mpd_menu_controls,
Widget.Box({
children: [
resource_dial(
(v) => `cpu: ${v}%`,
'microchip-solid',
1000,
"bash -c \"top -bn1 | grep 'Cpu(s)' | awk '{print \$2 + \$4}'\"",
v => v/100
),
resource_dial(
(v) => `mem: ${Math.round(100*v/31396)}%`,
'memory-solid',
1000,
"bash -c \"free -m | grep Mem | awk '{print $3}'\"",
v => v/31396
),
resource_dial(
(v) => `igpu: ${v}%`,
'expansion-card',
1000,
"bash -c \"cat /sys/class/drm/card1/device/gpu_busy_percent\"",
v => v/100
),
resource_dial(
(v) => `fan: ${v}rpm`,
'fan',
1000,
"bash -c \"sudo ectool pwmgetfanrpm | awk '{a+=\$4} END {print a/2}'\"",
v => v/5000
),
resource_dial(
(v) => `temp (cpu): ${v}`,
'thermometer',
1000,
"bash -c \"sudo ectool temps all | grep -E 'cpu' | awk '{print \$5}'\"",
v => v/100
),
]
})
]
})

View File

@ -1,185 +0,0 @@
const Notifications = await Service.import('notifications')
const { Pango } = imports.gi;
Notifications.popupTimeout = 3000;
function notification_icon({ app_entry, app_icon, image }) {
if (image) {
return Widget.Box({
css: `background-image: url("${image}");`
+ 'background-size: contain;'
+ 'background-repeat: no-repeat;'
+ 'background-position: center;',
})
}
let icon = 'dialog-information-symbolic'
if (Utils.lookUpIcon(app_icon))
icon = app_icon
if (app_entry && Utils.lookUpIcon(app_entry))
icon = app_entry
return Widget.Box({
child: Widget.Icon(icon),
})
}
const notification = (n) => {
const icon = Widget.Box({
vpack: 'center',
hpack: 'start',
class_name: 'icon',
child: notification_icon(n),
})
const title = Widget.Label({
className: 'title',
label: n.summary,
xalign: 0,
maxWidthChars: 40,
justify: 'left',
})
const body = Widget.Label({
className: 'body',
label: n.body,
xalign: 0,
justification: 'left',
maxWidthChars: 26,
wrap: true,
wrapMode: Pango.WrapMode.WORD_CHAR,
useMarkup: true,
})
const actions = Widget.Box({
class_name: "actions",
children: n.actions.map(({ id, label }) => Widget.Button({
class_name: "action-button",
on_clicked: () => {
n.invoke(id)
n.dismiss()
},
hexpand: true,
child: Widget.Label(label),
})),
})
// const buttons = Widget.Box({
// hpack: 'end',
// children: [
// Widget.Button({
// className: 'button',
// onClicked: () => n.dismiss(),
// child: Widget.Label('')
// })
// ]
// })
const timeout_progress = Widget.ProgressBar({
className: 'timeout-bar',
hexpand: true,
vpack: 'end',
value: 1,
setup: (self) => {
self.poll(n.timeout/100, () => {
if (self.value > 0.01) {
self.value = self.value - .01
}
else { n.dismiss() } // Notifications.forceTimeout doesn't work for notifications that are bugged.
})
}
})
const layout = Widget.Button({
child: Widget.Box({
children: [
Widget.Box({
className: 'notification',
vertical: true,
children: [
Widget.Box([
icon,
Widget.Box({
vertical: true,
children: [
title, body
]
})
]),
actions,
timeout_progress
]
}),
Widget.Box({
vexpand: false,
classNames: ['urgency-indicator', n.urgency]
})
]
}),
onPrimaryClick: n.dismiss,
})
return Widget.Revealer({
className: 'revealer',
attribute: { id: n.id },
vpack: 'start',
transition: 'slide_down',
transitionDuration: 250,
setup: (self) => {
Utils.timeout(1, () => self.set_reveal_child(true));
self.on('notify::reveal-child', () => {
if (self.reveal_child) Utils.timeout(125, () => self.child.set_reveal_child(true))
})
},
child: Widget.Revealer({
className: 'revealer',
hpack: 'end',
transition: 'slide_left',
transitionDuration: 250,
setup: (self) => self.on('notify::reveal-child', () => {
if (!self.reveal_child) Utils.timeout(250, () => self.parent.set_reveal_child(false))
}),
child: layout
})
})
}
export const show_notification_popups = Variable(true)
export const NotificationPopups = Widget.Window({
visible: show_notification_popups.bind(),
name: 'notifications',
anchor: ['top', 'right'],
layer: 'overlay',
margins: [15, 0, 0, 0],
child: Widget.Box({
className: 'notifications',
vertical: true,
widthRequest: 2,
heightRequest: 2,
children: Notifications.popups.map(notification),
setup: (self) => self.hook(Notifications, (_, id) => {
if (!id || Notifications.dnd) return;
const n = Notifications.getNotification(id)
if (!n) return;
self.children = [...self.children, notification(n)]
}, 'notified')
.hook(Notifications, (_, id) => {
if (!id) return;
const n = self.children.find(
(child) => child.attribute.id === id,
)
if (!n) return;
n.child.set_reveal_child(false)
Utils.timeout(n.child.transition_duration, () => n.destroy())
}, 'dismissed')
})
})

View File

@ -1,70 +0,0 @@
import { hyprworkspaces_grid } from '../widgets/hyprworkspaces.js'
import { volume_slider } from '../widgets/volume.js'
import { brightness_slider } from '../widgets/brightness.js'
import { battery_dial } from '../widgets/battery.js'
import { horizontal_clock } from '../widgets/clock.js'
import { active_window } from '../widgets/hypractive.js'
import { media } from '../widgets/mpris.js'
export const FakeBar = Widget.Window({
name: 'FakeBar',
exclusivity: 'exclusive',
anchor: ['left', 'top', 'right'],
margins: [35, 0],
child: Widget.Box({css: 'min-height: 1px;'})
})
const left_box = Widget.Box({
className: 'left',
hpack: 'start',
children: [
hyprworkspaces_grid,
media
]
})
const middle_box = Widget.Box({
className: 'middle',
hpack: 'center',
children: [
active_window
]
})
const right_box = Widget.Box({
className: 'right',
hpack: 'end',
children: [
Widget.Box({
className: 'sliderbox',
vertical: true,
children: [
volume_slider,
brightness_slider
]
}),
Widget.Box({
className: 'battery-container',
children: [battery_dial]
}),
Widget.Separator({vertical: true}),
horizontal_clock
]
})
const bar = Widget.Box({
className: 'bar',
children: [
left_box,
middle_box,
right_box
]
})
export const Bar = Widget.Window({
name: 'status-bar',
exclusivity: 'ignore',
// margins: [5, 5, 5, 5],
anchor: ['left', 'top', 'right'],
child: bar
})