initial commit, you are welcome odette
This commit is contained in:
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
|
||||
}
|
Reference in New Issue
Block a user