initial commit, you are welcome odette
This commit is contained in:
commit
af9d9e3451
64
config.js
Normal file
64
config.js
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})*/
|
1
icons/expansion-card.svg
Normal file
1
icons/expansion-card.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<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>
|
After Width: | Height: | Size: 238 B |
1
icons/fan.svg
Normal file
1
icons/fan.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<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>
|
After Width: | Height: | Size: 648 B |
1
icons/memory-solid.svg
Normal file
1
icons/memory-solid.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<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>
|
After Width: | Height: | Size: 990 B |
1
icons/microchip-solid.svg
Normal file
1
icons/microchip-solid.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<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>
|
After Width: | Height: | Size: 993 B |
1
icons/thermometer.svg
Normal file
1
icons/thermometer.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<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>
|
After Width: | Height: | Size: 163 B |
44
services/brightness.js
Normal file
44
services/brightness.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
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;
|
361
services/mpd.js
Normal file
361
services/mpd.js
Normal file
@ -0,0 +1,361 @@
|
|||||||
|
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;
|
31
style/colors.scss
Normal file
31
style/colors.scss
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
$bg: #161616;
|
||||||
|
$bg-alt-1: #262626;
|
||||||
|
$bg-alt-2: #393939;
|
||||||
|
|
||||||
|
$fg: #f2f4f8;
|
||||||
|
|
||||||
|
$hl: #FF7EB6;
|
||||||
|
$hl-alt-1: #33B1FF;
|
||||||
|
$hl-alt-2: #42be65;
|
||||||
|
|
||||||
|
$icon-color: #f2f4f8;
|
||||||
|
$button-hover-color: #FF7EB6;
|
||||||
|
|
||||||
|
$battery-dial-bg: #42be65;
|
||||||
|
$battery-dial-fg: #161616;
|
||||||
|
|
||||||
|
$volume-dial-bg: #33B1FF;
|
||||||
|
$volume-dial-fg: #161616;
|
||||||
|
|
||||||
|
$brightness-dial-bg: #33B1FF;
|
||||||
|
$brightness-dial-fg: #161616;
|
||||||
|
|
||||||
|
$ws-active: #FF7EB6;
|
||||||
|
$ws-inactive: $bg;
|
||||||
|
|
||||||
|
$mpd-progress-primary: #FF7Eb6;
|
||||||
|
|
||||||
|
$resource-dial-fg-cpu: #FF7eb6;
|
||||||
|
$resource-dial-fg-mem: #FF7eb6;
|
||||||
|
$resource-dial-fg-tmp: #FF7eb6;
|
||||||
|
$resource-dial-fg-fan: #FF7eb6;
|
68
style/components/bar-control-center.scss
Normal file
68
style/components/bar-control-center.scss
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
75
style/components/bar.scss
Normal file
75
style/components/bar.scss
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
* :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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
43
style/components/launcher.scss
Normal file
43
style/components/launcher.scss
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
28
style/components/lock.scss
Normal file
28
style/components/lock.scss
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
.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;
|
||||||
|
}
|
89
style/components/notification-popups.scss
Normal file
89
style/components/notification-popups.scss
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
#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: 2px solid $fg;
|
||||||
|
border-right: 25px solid $hl-alt-2;
|
||||||
|
background: $bg;
|
||||||
|
|
||||||
|
&.critical {
|
||||||
|
border-right: 25px solid $hl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeout-bar {
|
||||||
|
margin: 5px 0px 0;
|
||||||
|
margin-top: 5px;
|
||||||
|
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-right: 1em;
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon image {
|
||||||
|
font-size: 58px;
|
||||||
|
margin: 5px;
|
||||||
|
color: $fg;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon box {
|
||||||
|
min-width: 68px;
|
||||||
|
min-height: 68px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
126
style/components/top-bar.scss
Normal file
126
style/components/top-bar.scss
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
$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
Normal file
212
style/css
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
* :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; }
|
27
style/mixins.scss
Normal file
27
style/mixins.scss
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
@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;
|
||||||
|
}
|
||||||
|
}
|
315
style/style.css
Normal file
315
style/style.css
Normal file
@ -0,0 +1,315 @@
|
|||||||
|
.lock-ready > box box {
|
||||||
|
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: 2px solid #f2f4f8;
|
||||||
|
border-right: 25px solid #42be65;
|
||||||
|
background: #161616; }
|
||||||
|
.notifications .revealer .notification.critical {
|
||||||
|
border-right: 25px 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: 28px; }
|
||||||
|
.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; }
|
14
style/style.scss
Normal file
14
style/style.scss
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
@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;
|
||||||
|
}
|
29
utils/PopupWindow.js
Normal file
29
utils/PopupWindow.js
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
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;
|
||||||
|
}
|
38
widgets/battery.js
Normal file
38
widgets/battery.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
|
63
widgets/brightness.js
Normal file
63
widgets/brightness.js
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
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
|
||||||
|
}
|
62
widgets/clock.js
Normal file
62
widgets/clock.js
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
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
|
||||||
|
}
|
10
widgets/hypractive.js
Normal file
10
widgets/hypractive.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
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
|
||||||
|
}
|
59
widgets/hyprworkspaces.js
Normal file
59
widgets/hyprworkspaces.js
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
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
|
||||||
|
}
|
87
widgets/marqueeLabel.js
Normal file
87
widgets/marqueeLabel.js
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
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;
|
182
widgets/mpd.js
Normal file
182
widgets/mpd.js
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
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`;
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
114
widgets/mpris.js
Normal file
114
widgets/mpris.js
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
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,
|
||||||
|
});
|
24
widgets/resourceDial.js
Normal file
24
widgets/resourceDial.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
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
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
14
widgets/systray.js
Normal file
14
widgets/systray.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
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))
|
||||||
|
})
|
70
widgets/volume.js
Normal file
70
widgets/volume.js
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
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
|
||||||
|
}
|
141
windows/bar.js
Normal file
141
windows/bar.js
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
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
|
||||||
|
}
|
93
windows/launcher.js
Normal file
93
windows/launcher.js
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
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
|
||||||
|
}
|
76
windows/lock.js
Normal file
76
windows/lock.js
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
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,
|
||||||
|
})
|
49
windows/menu.js
Normal file
49
windows/menu.js
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
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
|
||||||
|
),
|
||||||
|
]
|
||||||
|
})
|
||||||
|
]
|
||||||
|
})
|
175
windows/notifications.js
Normal file
175
windows/notifications.js
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
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: 'start',
|
||||||
|
class_name: 'icon',
|
||||||
|
child: notification_icon(n),
|
||||||
|
})
|
||||||
|
|
||||||
|
const title = Widget.Label({
|
||||||
|
className: 'title',
|
||||||
|
label: n.summary,
|
||||||
|
xalign: 0,
|
||||||
|
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.Box({
|
||||||
|
className: `notification ${n.urgency}`,
|
||||||
|
vertical: true,
|
||||||
|
children: [
|
||||||
|
buttons,
|
||||||
|
Widget.Box([
|
||||||
|
icon,
|
||||||
|
Widget.Box({
|
||||||
|
vertical: true,
|
||||||
|
children: [
|
||||||
|
title, body
|
||||||
|
]
|
||||||
|
})
|
||||||
|
]),
|
||||||
|
actions,
|
||||||
|
timeout_progress
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
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: [5, 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')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
70
windows/top-bar.js
Normal file
70
windows/top-bar.js
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
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
|
||||||
|
})
|
Loading…
x
Reference in New Issue
Block a user