update ags

This commit is contained in:
sankalp committed 2024-06-26 00:10:21 +05:45
1 parent 88822cb8e8
commit a9c19bd2e1
134 files changed
+4732 -5813

No files matched your search

+179
View File
@@ -0,0 +1,179 @@
const { query } = await Service.import("applications")
const encoder = new TextEncoder();
const decoder = new TextDecoder();
const Gio = imports.gi.Gio;
const { Gtk } = imports.gi;
const GLib = imports.gi.GLib;
const WINDOW_NAME = "applauncher"
import Box from 'types/widgets/box';
import popupwindow from './misc/popupwindow.ts';
import { Application } from 'types/service/applications';
const LAUNCH_COUNT_FILE = Gio.File.new_for_path(
GLib.build_filenamev([GLib.get_home_dir(), '.cache', 'launch_counts.json'])
);
function readLaunchCounts() {
try {
if (!LAUNCH_COUNT_FILE.query_exists(null)) {
return {};
}
const [, contents] = LAUNCH_COUNT_FILE.load_contents(null);
return JSON.parse(decoder.decode(contents));
} catch (error) {
return {};
}
}
function writeLaunchCounts(launchCounts: number) {
const data = JSON.stringify(launchCounts, null, 2);
LAUNCH_COUNT_FILE.replace_contents(
encoder.encode(data),
null,
false,
Gio.FileCreateFlags.REPLACE_DESTINATION,
null
);
}
function incrementLaunchCount(appName: string) {
const launchCounts = readLaunchCounts();
launchCounts[appName] = (launchCounts[appName] || 0) + 1;
writeLaunchCounts(launchCounts);
}
function sortApplicationsByLaunchCount(applications: Box<any, any>[]) {
const launchCounts = readLaunchCounts()
return applications.sort((a: Box<any, any>, b: Box<any, any>) => {
const countA = launchCounts[a.attribute.app.name] || 0
const countB = launchCounts[b.attribute.app.name] || 0
return countB - countA
})
}
function AppItem(app: Application): Box<any, any> {
let clickCount = 0;
const button = Widget.Button({
class_name: "application_container",
child: Widget.Box({
children: [
Widget.Icon({
class_name: "application_icon",
// @ts-ignore
icon: Utils.lookUpIcon(app.icon_name) ? app.icon_name : "image-missing",
size: 42,
}),
Widget.Label({
class_name: "application_label",
label: app.name,
xalign: 0,
vpack: "center",
truncate: "end",
}),
]
})
})
button.connect('clicked', () => {
clickCount++;
if (clickCount === 2) {
incrementLaunchCount(app.name);
App.closeWindow(WINDOW_NAME);
app.launch();
clickCount = 0;
}
});
button.connect('focus-out-event', () => {
clickCount = 0;
});
return Widget.Box({
attribute: { app },
orientation: Gtk.Orientation.VERTICAL,
children: [
button,
Widget.Separator({
class_name: "application_divider",
orientation: Gtk.Orientation.HORIZONTAL
})
],
})
};
const Applauncher = ({ width = 500, height = 500, spacing = 12 }) => {
let applications: Box<any, any>[];
const list = Widget.Box({
vertical: true,
spacing,
})
function repopulate() {
applications = query("").map(AppItem)
applications = sortApplicationsByLaunchCount(applications)
list.children = applications
}
repopulate()
const entry = Widget.Entry({
hexpand: true,
class_name: "applauncher_entry",
placeholder_text: "Search",
on_accept: () => {
const results = applications.filter((item) => item.visible)
if (results[0]) {
App.toggleWindow(WINDOW_NAME)
results[0].attribute.app.launch()
}
},
on_change: ({ text }) => applications.forEach(item => {
item.visible = item.attribute.app.match(text ?? "")
}),
})
return Widget.Box({
vertical: true,
css: `margin: ${spacing * 2}px;`,
class_name: "applauncher_box",
margin_top: 14,
margin_left: 14,
children: [
entry,
Widget.Scrollable({
hscroll: "never",
css: `min-width: ${width}px;`
+ `min-height: ${height}px;`,
child: list,
}),
],
setup: self => self.hook(App, (_, windowName, visible) => {
if (windowName !== WINDOW_NAME)
return
if (visible) {
repopulate()
entry.text = ""
}
}),
})
}
export const applauncher = popupwindow({
name: WINDOW_NAME,
class_name: "applauncher",
visible: false,
keymode: "exclusive",
child: Applauncher({
width: 500,
height: 500,
spacing: 0,
}),
anchor: ["bottom", "left"]
})
+451
View File
@@ -0,0 +1,451 @@
const hyprland = await Service.import("hyprland")
const battery = await Service.import("battery")
const systemtray = await Service.import("systemtray")
const audio = await Service.import("audio")
const network = await Service.import("network")
const { Gtk, GLib, Gio } = imports.gi;
const encoder = new TextEncoder();
const decoder = new TextDecoder();
import { OpenSettings } from "apps/settings/main.ts";
import { enableClickThrough } from "./misc/clickthrough.js";
import { RoundedCorner } from "./misc/cairo_roundedcorner.js";
import { Client, Workspace } from "types/service/hyprland.js"
import Button from "types/widgets/button.js"
import Icon from "types/widgets/icon.js"
import { FileEnumerator, FileInfo } from "types/@girs/gio-2.0/gio-2.0.cjs"
const mpris = await Service.import("mpris")
function checkKeymap() {
const layout = Utils.execAsync(`hyprctl devices -j | jq -r '.keyboards[] | select(.main == true) | .active_keymap' | tail -n1 | cut -c1-2 | tr 'A-Z' 'a-z'`)
.then(out => { return out })
.catch(err => { print(err); return "en" });
return layout;
}
const keyboard_layout = Variable("en")
keyboard_layout.setValue(await checkKeymap())
hyprland.connect("keyboard-layout", (hyprland, keyboardname, layoutname) => {
keyboard_layout.setValue(layoutname.trim().toLowerCase().substr(0, 2))
})
const time = Variable("", {
poll: [1000, 'date "+%I:%M %p"'],
})
const date = Variable("", {
poll: [1000, 'date "+%Y-%m-%d"'],
})
function getIconNameFromClass(windowClass: string) {
let formattedClass = windowClass.replace(/\s+/g, '-').toLowerCase();
let homeDir = GLib.get_home_dir();
let systemDataDirs = GLib.get_system_data_dirs().map(dir => dir + '/applications');
let dataDirs = systemDataDirs.concat([homeDir + '/.local/share/applications']);
let icon: string | undefined;
for (let dir of dataDirs) {
let applicationsGFile = Gio.File.new_for_path(dir);
let enumerator: FileEnumerator;
try {
enumerator = applicationsGFile.enumerate_children('standard::name,standard::type', Gio.FileQueryInfoFlags.NONE, null);
} catch (e) {
continue;
}
let fileInfo: FileInfo | null;
while ((fileInfo = enumerator.next_file(null)) !== null) {
let desktopFile = fileInfo.get_name();
if (desktopFile.endsWith('.desktop')) {
let fileContents = GLib.file_get_contents(dir + '/' + desktopFile);
let matches = /Icon=(\S+)/.exec(decoder.decode(fileContents[1]));
if (matches && matches[1]) {
if (desktopFile.toLowerCase().includes(formattedClass)) {
icon = matches[1];
break;
}
}
}
}
enumerator.close(null);
if (icon) break;
}
return Utils.lookUpIcon(icon) ? icon : "image-missing";
}
const dispatch = (ws: string) => hyprland.messageAsync(`dispatch workspace ${ws}`).catch(print);
function Workspaces() {
const activeId = hyprland.active.workspace.bind("id");
let workspaceButtons = new Map();
function createWorkspaceButton(id: Number) {
const button = Widget.Button({
on_clicked: () => dispatch(`${id}`),
child: Widget.Label(`${id}`),
class_name: activeId.as(i => `${i === id ? "active" : ""}`),
});
return button;
}
function updateWorkspaceButtons(workspaces: Workspace[]): Array<Button<any, any>> {
workspaces.sort((a, b) => a.id - b.id);
const updatedButtons = new Map();
workspaces.forEach(({ id }) => {
if (workspaceButtons.has(id)) {
updatedButtons.set(id, workspaceButtons.get(id));
} else {
updatedButtons.set(id, createWorkspaceButton(id));
}
});
if (workspaceButtons != updatedButtons)
workspaceButtons = updatedButtons;
return Array.from(workspaceButtons.values());
}
const workspaceButtonsArray = hyprland.bind("workspaces").as(updateWorkspaceButtons);
return Widget.EventBox({
onScrollUp: () => dispatch('+1'),
onScrollDown: () => dispatch('-1'),
child: Widget.Box({
children: workspaceButtonsArray,
class_name: "workspaces",
}),
});
}
function Clock() {
return Widget.Box({
orientation: Gtk.Orientation.VERTICAL,
class_name: "clock",
children: [
Widget.Label({
class_name: "time",
label: time.bind(),
}),
Widget.Label({
class_name: "date",
label: date.bind(),
})
],
})
}
function BatteryLabel() {
const icon = battery.bind("icon_name");
const isVisible = battery.bind("percent").as(p => p < 100);
return Widget.Box({
class_name: "battery",
visible: isVisible,
children: [
Widget.Icon({ icon }),
Widget.Label({
label: battery.bind("percent").as(p => `${p > 0 ? p : 0}%`),
}),
],
});
}
function SysTray() {
const items = systemtray.bind("items")
.as(items => items.map(item => {
if (item.id.trim() != "nm-applet") {
return Widget.Button({
child: Widget.Icon({ icon: item.bind("icon") }),
on_primary_click: (_, event) => item.activate(event),
on_secondary_click: (_, event) => item.openMenu(event),
tooltip_markup: item.bind("tooltip_markup"),
})
} else {
return undefined
}
}))
// @ts-ignore
return Widget.Box({
class_name: "tray",
spacing: 5,
children: items,
})
}
function AppLauncher() {
const button = Widget.Button({
class_name: "filled_tonal_button awesome_icon",
on_clicked: () => {
App.toggleWindow("applauncher")
},
child: Widget.Label(
"\uf002"
)
})
return button
}
function Wifi() {
const button = Widget.Button({
class_name: "bar_wifi awesome_icon",
on_primary_click: () => {
OpenSettings("network")
},
on_secondary_click: (_, event) => {
const nm_applet = systemtray.items.find(item => item.id == "nm-applet")
if (nm_applet) {
nm_applet.openMenu(event)
} else {
Utils.execAsync("nm-connection-editor").catch(print)
}
},
child: Widget.Icon({
icon: "network-wireless-offline-symbolic"
}),
tooltip_text: 'Disabled'
}).hook(network, self => {
if (network.wifi.enabled) {
self.tooltip_text = network.wifi.ssid || 'Unknown';
self.child.icon = network.wifi.icon_name;
} else {
self.tooltip_text = 'Disabled'
self.child.icon = "network-wireless-offline-symbolic";
}
})
return button
}
function MediaPlayer() {
let metadata = mpris.players[0]?.metadata;
const button = Widget.Button({
class_name: "filled_tonal_button awesome_icon",
on_primary_click: () => {
App.toggleWindow("media")
// Utils.execAsync(["ags", "-t", "media"])
},
on_secondary_click: () => {
Utils.execAsync(["playerctl", "play-pause"]).catch(print)
},
child: Widget.Label(
""
),
visible: false,
tooltip_text: "Unknown"
}).hook(mpris, self => {
if (mpris.players.length > 0) {
self.visible = true;
metadata = mpris.players[0]?.metadata;
if (metadata)
self.tooltip_text = `${metadata["xesam:artist"]} - ${metadata["xesam:title"]}`;
} else {
self.visible = false;
self.tooltip_text = "Unknown"
App.closeWindow("media")
}
})
return button
}
function KeyboardLayout() {
const widget = Widget.Label({
class_name: "keyboard",
label: keyboard_layout.bind()
})
return widget
}
function OpenSideBar() {
const button = Widget.Button({
class_name: "filled_tonal_button awesome_icon",
on_clicked: () => {
App.toggleWindow("sidebar")
// Utils.execAsync(["ags", "-t", "sidebar"])
},
label: ""
})
return button
}
function TaskBar() {
let globalWidgets: Button<Icon<any>, any>[] = [];
function Clients(clients: Client[]) {
const currentClientIds = clients.map(client => client.pid);
globalWidgets = globalWidgets.filter(widget => currentClientIds.includes(widget.attribute.pid));
clients.forEach(item => {
let widget = globalWidgets.find(w => w.attribute.pid === item.pid);
//if (item.class == "kitty") {
// return;
//}
if (widget) {
widget.tooltip_markup = item.title;
} else {
let icon: string | undefined;
if (item.class == "com.github.Aylur.ags") {
if (item.initialTitle == "Settings") {
icon = "emblem-system-symbolic"
}
}
else
icon = getIconNameFromClass(item.class)
widget = Widget.Button({
attribute: { pid: item.pid },
child: Widget.Icon({ icon: icon }),
tooltip_markup: item.title,
});
globalWidgets.push(widget);
}
});
return globalWidgets;
}
return Widget.Box({
class_name: "tray",
spacing: 5,
children: hyprland.bind("clients").as(Clients),
})
}
function volumeIndicator() {
return Widget.EventBox({
onScrollUp: () => audio.speaker.volume += 0.01,
onScrollDown: () => audio.speaker.volume -= 0.01,
class_name: "filled_tonal_button volume_box",
child: Widget.Button({
on_primary_click: () => Utils.execAsync("pavucontrol").catch(print),
on_secondary_click: () => audio.speaker.is_muted = !audio.speaker.is_muted,
child: Widget.Box({
children: [
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]) => Number(threshold) <= vol)?.[1];
if (audio.speaker.is_muted)
self.icon = `audio-volume-muted-symbolic`;
else
self.icon = `audio-volume-${icon}-symbolic`;
self.tooltip_text = `Volume ${Math.floor(vol)}%`;
}),
Widget.Label({
label: audio.speaker.bind("volume").as(volume => `${Math.floor(volume * 100)}%`),
})
]
})
})
})
}
function Left() {
return Widget.Box({
// margin_left: 15,
class_name: "modules-left",
hpack: "start",
spacing: 8,
children: [
AppLauncher(),
MediaPlayer(),
TaskBar()
],
})
}
function Center() {
return Widget.Box({
class_name: "modules-center",
hpack: "center",
spacing: 8,
children: [
Workspaces(),
],
})
}
function Right() {
return Widget.Box({
// margin_right: 15,
class_name: "modules-right",
hpack: "end",
spacing: 8,
children: [
volumeIndicator(),
KeyboardLayout(),
BatteryLabel(),
SysTray(),
Wifi(),
Clock(),
OpenSideBar()
],
})
}
export const Bar = async (monitor = 0) => {
return Widget.Window({
name: `bar-${monitor}`,
class_name: "bar",
monitor,
//anchor: ["top", "left", "right"],
anchor: ["bottom", "left", "right"],
exclusivity: "exclusive",
child: Widget.CenterBox({
start_widget: Left(),
center_widget: Center(),
end_widget: Right(),
}),
})
}
export const BarCornerTopLeft = (monitor = 0) => Widget.Window({
monitor,
name: `bar_corner_tl${monitor}`,
class_name: "transparent",
layer: 'top',
anchor: ['top', 'left'],
exclusivity: 'normal',
visible: true,
child: RoundedCorner('top_left', { className: 'corner', }),
setup: enableClickThrough,
});
export const BarCornerTopRight = (monitor = 0) => Widget.Window({
monitor,
name: `bar_corner_tr${monitor}`,
class_name: "transparent",
layer: 'top',
anchor: ['top', 'right'],
exclusivity: 'normal',
visible: true,
child: RoundedCorner('top_right', { className: 'corner', }),
setup: enableClickThrough,
});
+127
View File
@@ -0,0 +1,127 @@
const { GLib } = imports.gi;
const WINDOW_NAME = "cliphist"
import popupwindow from './misc/popupwindow.ts';
import Box from 'types/widgets/box.js';
const { Gtk } = imports.gi;
type EntryObject = {
id: string;
content: string;
entry: string;
};
function ClipHistItem(entry: string) {
let [id, ...content] = entry.split('\t');
let clickCount = 0;
let button = Widget.Button({
class_name: "entry_container",
child: Widget.Label({
label: entry,
class_name: "entry_label",
xalign: 0,
vpack: "center",
truncate: "end",
})
});
button.connect('clicked', () => {
clickCount++;
if (clickCount === 1) {
App.closeWindow(WINDOW_NAME);
Utils.execAsync(`${App.configDir}/scripts/cliphist.sh --copy-by-id ${id}`);
clickCount = 0;
}
});
button.connect('focus-out-event', () => {
clickCount = 0;
});
return Widget.Box({
attribute: { content: content.join(' ').trim() },
orientation: Gtk.Orientation.VERTICAL,
children: [
button,
Widget.Separator({
class_name: "entry_divider",
orientation: Gtk.Orientation.HORIZONTAL,
}),
],
});
}
function ClipHistWidget({ width = 500, height = 500, spacing = 12 }) {
let output: string;
let entries: string[];
let clipHistItems: EntryObject[];
let widgets: Box<any, any>[];
const list = Widget.Box({
vertical: true,
spacing,
});
async function repopulate() {
output = await Utils.execAsync(`${App.configDir}/scripts/cliphist.sh --get`)
.then(str => str)
.catch(err => {
print(err);
return "";
});
entries = output.split('\n').filter(line => line.trim() !== '');
clipHistItems = entries.map(entry => {
let [id, ...content] = entry.split('\t');
return { id: id.trim(), content: content.join(' ').trim(), entry: entry };
});
widgets = clipHistItems.map(item => ClipHistItem(item.entry));
list.children = widgets;
}
repopulate();
const entry = Widget.Entry({
hexpand: true,
class_name: "cliphistory_entry",
placeholder_text: "Search",
on_change: ({ text }) => {
const lowerCaseText = (text ?? "").toLowerCase();
widgets.forEach(item => {
item.visible = item.attribute.content.toLowerCase().includes(lowerCaseText);
});
},
});
return Widget.Box({
vertical: true,
class_name: "cliphistory_box",
margin_top: 14,
margin_right: 14,
children: [
entry,
Widget.Scrollable({
hscroll: "never",
css: `min-width: ${width}px; min-height: ${height}px;`,
child: list,
}),
],
setup: self => self.hook(App, (_, windowName, visible) => {
if (windowName !== WINDOW_NAME) return;
if (visible) {
repopulate();
entry.text = "";
}
}),
});
}
export const cliphist = popupwindow({
name: WINDOW_NAME,
class_name: "cliphistory",
visible: false,
keymode: "exclusive",
child: ClipHistWidget({
width: 500,
height: 500,
spacing: 0,
}),
anchor: ["bottom", "right"],
});
+172
View File
@@ -0,0 +1,172 @@
import { MprisPlayer } from 'types/service/mpris.js';
import popupwindow from './misc/popupwindow.ts';
import Label from 'types/widgets/label.js';
const mpris = await Service.import("mpris")
const players = mpris.bind("players")
const FALLBACK_ICON = "audio-x-generic-symbolic"
const PLAY_ICON = "media-playback-start-symbolic"
const PAUSE_ICON = "media-playback-pause-symbolic"
const PREV_ICON = "media-skip-backward-symbolic"
const NEXT_ICON = "media-skip-forward-symbolic"
const WINDOW_NAME = "media"
function lengthStr(length: number): string {
const min = Math.floor(length / 60)
const sec = Math.floor(length % 60)
const sec0 = sec < 10 ? "0" : ""
return `${min}:${sec0}${sec}`
}
function Player(player: MprisPlayer) {
const img = Widget.Box({
class_name: "img",
vpack: "start",
css: player.bind("cover_path").transform(p => `
background-image: url('${p}');
`),
})
const title = Widget.Label({
class_name: "title",
wrap: true,
hpack: "start",
label: player.bind("track_title"),
})
const artist = Widget.Label({
class_name: "artist",
wrap: true,
hpack: "start",
label: player.bind("track_artists").transform(a => a.join(", ")),
})
const positionSlider = Widget.Slider({
class_name: "position",
draw_value: false,
on_change: ({ value }) => player.position = value * player.length,
visible: player.bind("length").as(l => l > 0),
setup: self => {
function update() {
const value = player.position / player.length
self.value = value > 0 ? value : 0
}
self.hook(player, update)
self.hook(player, update, "position")
self.poll(1000, update)
},
})
const positionLabel = Widget.Label({
class_name: "position",
hpack: "start",
setup: self => {
const update = (self: Label<any>) => {
self.label = lengthStr(player.position)
self.visible = player.length > 0
}
self.hook(player, update, "position")
self.poll(1000, update)
},
})
const lengthLabel = Widget.Label({
class_name: "length",
hpack: "end",
visible: player.bind("length").transform(l => l > 0),
label: player.bind("length").transform(lengthStr),
})
const icon = Widget.Icon({
class_name: "icon",
hexpand: true,
hpack: "end",
vpack: "start",
tooltip_text: player.identity || "",
icon: player.bind("entry").transform(entry => {
const name = `${entry}-symbolic`
return Utils.lookUpIcon(name) ? name : FALLBACK_ICON
}),
})
const playPause = Widget.Button({
class_name: "play-pause",
on_clicked: () => player.playPause(),
visible: player.bind("can_play"),
child: Widget.Icon({
icon: player.bind("play_back_status").transform(s => {
switch (s) {
case "Playing": return PAUSE_ICON
case "Paused":
case "Stopped": return PLAY_ICON
}
}),
}),
})
const prev = Widget.Button({
on_clicked: () => player.previous(),
visible: player.bind("can_go_prev"),
child: Widget.Icon(PREV_ICON),
})
const next = Widget.Button({
on_clicked: () => player.next(),
visible: player.bind("can_go_next"),
child: Widget.Icon(NEXT_ICON),
})
return Widget.Box(
{
class_name: "player",
margin_left: 14,
margin_top: 14,
},
img,
Widget.Box(
{
vertical: true,
hexpand: true,
},
Widget.Box([
title,
icon,
]),
artist,
Widget.Box({ vexpand: true }),
positionSlider,
Widget.CenterBox({
start_widget: positionLabel,
center_widget: Widget.Box({
class_name: "player_buttons",
children: [
prev,
playPause,
next,
]
}),
end_widget: lengthLabel,
}),
),
)
}
export function Media() {
return Widget.Box({
vertical: true,
css: "min-height: 2px; min-width: 2px;",
visible: players.as(p => p.length > 0),
children: players.as(p => p.map(Player)),
})
}
export const media = popupwindow({
name: WINDOW_NAME,
class_name: "player_window",
visible: false,
keymode: "exclusive",
child: Media(),
//anchor: ["left", "top"],
anchor: ["left", "bottom"],
})
+114
View File
@@ -0,0 +1,114 @@
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
const prefix = "100" // Identifier for minimized workspace
function getMinimizedWS(maximizedWS){
return parseInt(prefix + maximizedWS)
}
function getMaximizedWS(minimizedWS){
return parseInt(minimizedWS.toString().substring(prefix.length))
}
function isMinimized(ws){
ws = ws.toString()
if (ws.length > 3 || ws.slice(0, prefix.length) == prefix){
return true
}
else{
return false
}
}
const focusClient = (client) => {
// If minimized
if (isMinimized(client.workspace.id)){
var maximizedWS = getMaximizedWS(client.workspace.id)
Hyprland.messageAsync(`dispatch movetoworkspacesilent ${maximizedWS},address:${client.address}`)
// Focus window
//Hyprland.messageAsync(`dispatch focuswindow address:${client.address}`)
Hyprland.messageAsync(`dispatch alterzorder top,address:${client.address}`)
}
// If maximized
else{
var minimizedWS = getMinimizedWS(client.workspace.id)
Hyprland.messageAsync(`dispatch movetoworkspacesilent ${minimizedWS},address:${client.address}`)
}
}
const Client = (client = null) => Widget.Box({
// Attributes
attribute: {
returnWS: client.workspace,
},
})
const appButton = (client = null) => Widget.Button({
class_name: "dock-button",
tooltip_text: client.title,
on_primary_click: () => focusClient(client),
child: Widget.Box({
vertical: true,
children: [
Widget.Box({
children: [
Widget.Box({
class_name: "dock-app-indicator",
vpack: "center",
setup: (self) => {
self.hook(Hyprland, () => {
//self.toggleClassName("dock-button-current", Hyprland.active.client.address === client.address)
self.toggleClassName("dock-app-indicator-active", Hyprland.active.client.address === client.address)
}, 'event')
}
}),
/*
Widget.Icon({
class_name: 'client-icon',
css: 'font-size: 3rem;',
icon: client.class,
}),
*/
],
}),
Widget.Label({
label: client.class,
truncate: 'end',
maxWidthChars: 8,
})
],
}),
});
const clientList = Widget.Scrollable({
hscroll: "never",
child: Widget.Box({
class_name: "dock-container",
css: "min-height: 6rem;",
css: "min-width: 6rem;",
vertical: true,
}).hook(Hyprland, self => {
//check if ws is empty
self.children = Hyprland.clients.filter(client => client.class != "" && (client.workspace.id === Hyprland.active.workspace.id || client.workspace.id === getMinimizedWS(Hyprland.active.workspace.id))).map(client => {
return appButton(client)
})
})
})
export const Dock = (monitor = 0) => Widget.Window({
name: `Dock`, // name has to be unique
monitor,
visible: false,
//margins: [4,64,4,64],
//margins: [8,8,8,8],
anchor: ['bottom', 'left', 'top'],
exclusivity: 'exclusive',
child: clientList,
});
@@ -0,0 +1,51 @@
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
const { Gtk } = imports.gi;
const Lang = imports.lang;
export const RoundedCorner = (place, props) => Widget.DrawingArea({
...props,
hpack: place.includes('left') ? 'start' : 'end',
vpack: place.includes('top') ? 'start' : 'end',
setup: (widget) => Utils.timeout(1, () => {
const c = widget.get_style_context().get_property('background-color', Gtk.StateFlags.NORMAL);
const r = widget.get_style_context().get_property('border-radius', Gtk.StateFlags.NORMAL);
widget.set_size_request(r, r);
// @ts-ignore
widget.connect('draw', Lang.bind(widget, (widget, cr) => {
const c = widget.get_style_context().get_property('background-color', Gtk.StateFlags.NORMAL);
const r = widget.get_style_context().get_property('border-radius', Gtk.StateFlags.NORMAL);
// const borderColor = widget.get_style_context().get_property('color', Gtk.StateFlags.NORMAL);
// const borderWidth = widget.get_style_context().get_border(Gtk.StateFlags.NORMAL).left; // ur going to write border-width: something anyway
widget.set_size_request(r, r);
switch (place) {
case 'top_left':
cr.arc(r, r, r, Math.PI, 3 * Math.PI / 2);
cr.lineTo(0, 0);
break;
case 'top_right':
cr.arc(0, r, r, 3 * Math.PI / 2, 2 * Math.PI);
cr.lineTo(r, 0);
break;
case 'bottom_left':
cr.arc(r, 0, r, Math.PI / 2, Math.PI);
cr.lineTo(0, r);
break;
case 'bottom_right':
cr.arc(0, 0, r, 0, Math.PI / 2);
cr.lineTo(r, r);
break;
}
cr.closePath();
cr.setSourceRGBA(c.red, c.green, c.blue, c.alpha);
cr.fill();
// cr.setLineWidth(borderWidth);
// cr.setSourceRGBA(borderColor.red, borderColor.green, borderColor.blue, borderColor.alpha);
// cr.stroke();
}));
}),
});
+4
View File
@@ -0,0 +1,4 @@
import Cairo from 'gi://cairo?version=1.0';
export const dummyRegion = new Cairo.Region();
export const enableClickThrough = (self) => self.input_shape_combine_region(dummyRegion);
+44
View File
@@ -0,0 +1,44 @@
import App from 'resource:///com/github/Aylur/ags/app.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import { WindowProps, Window } from 'types/widgets/window';
const { Gtk, GLib } = imports.gi;
type _WindowProps = WindowProps<any, unknown, Window<any, unknown>> | undefined[];
type Type = {
name: string,
child: any,
showClassName?: string,
hideClassName?: string,
}
export default <T extends Type>({
name,
child,
showClassName = "",
hideClassName = "",
...props
}: T & Omit<_WindowProps, keyof Type>) => {
return Widget.Window({
name,
visible: false,
type: Gtk.WindowType.POPUP,
layer: 'overlay',
resizable: true,
...props,
setup: (self) => {
self.keybind("Escape", () => App.closeWindow(name));
if (showClassName != "" && hideClassName !== "") {
self.hook(App, (self, currentName, visible) => {
if (currentName === name) {
self.toggleClassName(hideClassName, !visible);
}
});
if (showClassName !== "" && hideClassName !== "")
self.class_name = `${showClassName} ${hideClassName}`;
}
},
child: child,
});
}
+257
View File
@@ -0,0 +1,257 @@
import { Gtk as GtkType } from "types/@girs/gtk-3.0/gtk-3.0";
import { Notification as NotificationType } from "types/service/notifications";
import Box from "types/widgets/box";
import { timeout } from "resource:///com/github/Aylur/ags/utils.js";
import GLib from "gi://GLib";
import Pango from "gi://Pango";
import Label from "types/widgets/label";
const notifications = await Service.import("notifications")
const { Gtk } = imports.gi;
function NotificationIcon({ app_entry, app_icon, image }: NotificationType): Box<GtkType.Widget, any> {
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),
})
}
export const Notification = (notification: NotificationType, dismiss = true) => Widget.Box({
class_name: `notification ${notification.urgency}`,
vertical: true,
children: [
Widget.EventBox({
on_primary_click: (box) => {
// @ts-ignore
const label: Label<any> = box.child.children[1].children[1];
if (label.lines < 0) {
label.lines = 3;
label.truncate = "end";
} else {
label.lines = -1;
label.truncate = "none";
}
},
child: Widget.Box({
children: [
Widget.Box({
class_name: "notification-icon",
child: NotificationIcon(notification),
vpack: "start"
}),
Widget.Box({
vertical: true,
children: [
Widget.Box({
children: [
Widget.Label({
class_name: "notification-title",
label: notification.summary,
justification: "left",
max_width_chars: 3,
truncate: "end",
lines: 1,
xalign: 0,
hexpand: true,
}),
Widget.Label({
class_name: "notification-time",
label: GLib.DateTime.new_from_unix_local(notification.time).format("%I:%M %p"),
}),
Widget.Button({
class_name: "standard_icon_button",
child: Widget.Icon("window-close-symbolic"),
on_clicked: () => {
if (dismiss) {
notification.dismiss();
}
else {
notification.close();
}
},
css: "margin-left: 5px;"
})
]
}),
Widget.Label({
class_name: "notification-body",
justification: "left",
max_width_chars: 24,
lines: 3,
truncate: "end",
wrap_mode: Pango.WrapMode.WORD_CHAR,
xalign: 0,
wrap: true,
// HACK: remove linebreaks, so lines property works properly
label: notification.body.replace(/(\r\n|\n|\r)/gm, " "),
}),
notification.hints.value ?
Widget.ProgressBar({
class_name: "notification-progress",
value: Number(notification.hints.value.unpack()) / 100
}) : Widget.Box()
]
})
]
})
}),
Widget.Box({
halign: Gtk.Align.END,
class_name: "notification-actions",
children: notification.actions.map(action => Widget.Button({
child: Widget.Label(action.label),
on_clicked: () => notification.invoke(action.id),
class_name: "notification-action-button",
}))
})
]
});
const NotificationReveal = (notification: NotificationType, visible = false, dismiss = true) => {
const transition_duration = 200;
const secondRevealer = Widget.Revealer({
vpack: "start",
child: Notification(notification, dismiss),
reveal_child: visible,
transition: "slide_left",
transition_duration: transition_duration,
hexpand: true,
class_name: "second_revealer",
setup: (revealer) => {
timeout(1, () => {
revealer.reveal_child = true;
});
},
});
const firstRevealer = Widget.Revealer({
child: secondRevealer,
reveal_child: true,
transition: "slide_down",
transition_duration: transition_duration,
class_name: "first_revealer"
});
type BoxAttrs = {
destroyWithAnims: any,
count: number,
id: number,
}
let box: Box<any, BoxAttrs>;
const destroyWithAnims = () => {
secondRevealer.reveal_child = false;
timeout(transition_duration, () => {
firstRevealer.reveal_child = false;
timeout(transition_duration, () => {
box.destroy();
});
});
};
box = Widget.Box({
hexpand: true,
hpack: "end",
attribute: {
"destroyWithAnims": destroyWithAnims,
count: 0,
id: notification.id,
},
children: [firstRevealer],
});
return box;
};
type NotificationsBoxType = {
exclude: string[],
include: string[]
}
export function NotificationPopups(
dismiss = true,
{ exclude, include }: NotificationsBoxType = { exclude: [], include: [] },
timeout = true,
revealer = NotificationReveal
) {
const list = Widget.Box({
vpack: "start",
vertical: true,
children: notifications.popups.map(id => revealer(id, false, dismiss)),
})
function onNotified(_: any, id: number) {
const n = notifications.getNotification(id)
if (notifications.dnd || !n)
return;
if (exclude.includes(n.app_name.trim()))
return
if (!include.includes(n.app_name.trim()) && include.length > 0)
return
const original = list.children.find(n => n.attribute.id === id);
const replace = original?.attribute.id;
if (!replace) {
const notification = revealer(n, false, dismiss);
notification.attribute.count = 1;
list.pack_end(notification, false, false, 0)
}
else if (original) {
const notification = revealer(n, true, dismiss);
notification.attribute.count++;
original.destroy()
list.pack_end(notification, false, false, 0)
}
if (timeout)
setTimeout(() => {
onDismissed(_, id)
}, 5000)
}
function onDismissed(_: any, id: number) {
const original = list.children.find(n => n.attribute.id === id);
if (!original)
return
original.attribute.count--;
if (original.attribute.count <= 0) {
original.attribute.destroyWithAnims()
}
}
list.hook(notifications, onNotified, "notified")
.hook(notifications, onDismissed, dismiss ? "dismissed" : "closed")
return list
}
export function Notifications(monitor = 0) {
const window = Widget.Window({
monitor,
name: `notifications${monitor}`,
class_name: "notification-popups",
anchor: ["bottom", "right"],
type: Gtk.WindowType.POPUP,
child: Widget.Box({
css: "min-width: 2px; min-height: 2px;",
class_name: "notifications",
hexpand: true,
vexpand: true,
child: NotificationPopups(),
}),
visible: true,
})
return window
}
+178
View File
@@ -0,0 +1,178 @@
const { Gtk, GLib } = imports.gi;
const lock_command = `hyprlock`;
const logout_command = `loginctl kill-session $XDG_SESSION_ID`;
const shutdown_command = `systemctl poweroff`;
const reboot_command = `systemctl reboot`;
const suspend_command = `systemctl suspend`;
function LockButton({ icon, ...props }) {
let clickCount = 0;
let button = Widget.Button({
tooltip_text: "Lock",
child: Widget.Label({
label: icon,
class_name: "awesome_icon"
}),
class_name: "outline_button",
...props,
})
button.connect('clicked', () => {
clickCount++;
if (clickCount === 2) {
Utils.execAsync(lock_command).catch(print);
clickCount = 0;
}
});
button.connect('focus-out-event', () => {
clickCount = 0;
});
return button
}
function SuspendButton({ icon, ...props }) {
let clickCount = 0;
let button = Widget.Button({
tooltip_text: "Suspend",
child: Widget.Label({
label: icon,
class_name: "awesome_icon"
}),
class_name: "outline_button",
...props,
})
button.connect('clicked', () => {
clickCount++;
if (clickCount === 2) {
Utils.execAsync(`mpc -q pause`).catch();
Utils.execAsync(`playerctl pause`).catch();
Utils.execAsync(suspend_command).catch();
clickCount = 0;
}
});
button.connect('focus-out-event', () => {
clickCount = 0;
});
return button
}
function LogoutButton({ icon, ...props }) {
let clickCount = 0;
let button = Widget.Button({
tooltip_text: "Logout",
child: Widget.Label({
label: icon,
class_name: "awesome_icon"
}),
class_name: "outline_button",
...props,
})
button.connect('clicked', () => {
clickCount++;
if (clickCount === 2) {
Utils.execAsync(logout_command).catch();
clickCount = 0;
}
});
button.connect('focus-out-event', () => {
clickCount = 0;
});
return button
}
function RebootButton({ icon, ...props }) {
let clickCount = 0;
let button = Widget.Button({
tooltip_text: "Reboot",
child: Widget.Label({
label: icon,
class_name: "awesome_icon"
}),
class_name: "outline_button",
...props,
})
button.connect('clicked', () => {
clickCount++;
if (clickCount === 2) {
Utils.execAsync(reboot_command).catch();
clickCount = 0;
}
});
button.connect('focus-out-event', () => {
clickCount = 0;
});
return button
}
function ShutdownButton({ icon, ...props }) {
let clickCount = 0;
let button = Widget.Button({
tooltip_text: "Shutdown",
child: Widget.Label({
label: icon,
class_name: "awesome_icon"
}),
class_name: "outline_button",
...props,
})
button.connect('clicked', () => {
clickCount++;
if (clickCount === 2) {
Utils.execAsync(shutdown_command).catch();
clickCount = 0;
}
});
button.connect('focus-out-event', () => {
clickCount = 0;
});
return button
}
export function Buttons() {
return Widget.Box({
class_name: "sidebar_buttons",
hexpand: true,
spacing: 5,
children: [
LockButton({
icon: "",
hexpand: true,
}),
SuspendButton({
icon: "",
hexpand: true,
}),
LogoutButton({
icon: "",
hexpand: true,
}),
RebootButton({
icon: "",
hexpand: true,
}),
ShutdownButton({
icon: "",
hexpand: true,
})
]
})
}
+16
View File
@@ -0,0 +1,16 @@
import popupwindow from '../misc/popupwindow.js';
import { SideBar } from './sidebar.ts'
export const WINDOW_NAME = "sidebar"
export const sidebar = popupwindow({
name: WINDOW_NAME,
class_name: "sidebar",
visible: false,
keymode: "exclusive",
child: SideBar(),
anchor: ["top", "right", "bottom"],
css: "min-width: 443px;"
})
+387
View File
@@ -0,0 +1,387 @@
const notifications = await Service.import("notifications")
const network = await Service.import('network')
import { OpenSettings } from "apps/settings/main.ts";
import { WINDOW_NAME } from "./main.ts"
const { Gtk } = imports.gi;
const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const idle_inhibitor = Variable(false);
const night_light = Variable(false);
const screen_recorder = Variable(false);
const currentPage = Variable(0);
export const bluetooth_enabled = Variable('off', {
poll: [1000, `${App.configDir}/scripts/bluetooth.sh --get`]
})
const powerProfiles = await Service.import('powerprofiles');
const icons = {
'performance': ' ',
'balanced': ' ',
'power-saver': '󰡵 '
};
function WifiIndicator() {
return Widget.Box({
css: "padding-left: 15px; padding-right: 15px; padding-top: 5px; padding-bottom: 5px;",
children: [
Widget.Label({
label: "\udb82\udd28",
class_name: "awesome_icon",
css: `font-weight: bold; font-size: 20px; margin-right: 0.60em;`,
}),
Widget.Box({
visible: network.wifi.bind('enabled'),
orientation: Gtk.Orientation.VERTICAL,
children: [
Widget.Box([
Widget.Label({
label: "Internet",
}),
]),
Widget.Label({
label: network.wifi.bind('ssid')
.as(ssid => ssid || 'Unknown'),
class_name: "ssid",
xalign: 0,
vpack: "center",
truncate: "end",
}),
]
}),
Widget.Label({
visible: network.wifi.bind('enabled').as(enabled => !enabled),
label: "Internet",
}),
Widget.Box({
halign: Gtk.Align.END,
hexpand: true,
child: Widget.Label({
label: "",
class_name: "awesome_icon",
css: `font-weight: normal; font-size: 15px;`,
})
})
],
})
}
const WiredIndicator = () => Widget.Box({
css: "padding-left: 15px; padding-right: 15px; padding-top: 5px; padding-bottom: 5px;",
children: [
Widget.Icon({
icon: network.wired.bind('icon_name'),
}),
Widget.Label({
label: "Internet",
}),
]
})
const NetworkIndicator = () => Widget.Stack({
children: {
wifi: WifiIndicator(),
wired: WiredIndicator(),
},
shown: network.bind('primary').as(p => p || 'wifi'),
})
function IconAndName({ label, icon, padding = "0.3em", arrow = false }) {
let box = Widget.Box({
css: "padding-left: 15px; padding-right: 15px; padding-top: 5px; padding-bottom: 5px;",
children: [
Widget.Label({
label: icon,
class_name: "awesome_icon",
css: `font-weight: bold; font-size: 20px; margin-right: ${padding};`,
}),
Widget.Label({
label: label,
justification: "center",
}),
]
})
if (arrow) {
const arrow = Widget.Box({
halign: Gtk.Align.END,
hexpand: true,
child: Widget.Label({
label: "",
class_name: "awesome_icon",
css: `font-weight: normal; font-size: 15px;`,
})
})
// @ts-ignore
box.children = [...box.children, arrow]
}
return box
}
function isScreenRecordingOn() {
// Check if wf-recorder is running
// Bash command outputs 0 if yes or 1 if no
let state = Utils.execAsync('bash -c "pidof wf-recorder > /dev/null; echo $?"')
if (state == "0"){
return true
}
else{
return false
}
}
function Page1() {
return Widget.Box({
orientation: Gtk.Orientation.VERTICAL,
spacing: 5,
hexpand: true,
class_name: "management_box",
children: [
Widget.Box({
orientation: Gtk.Orientation.HORIZONTAL,
hexpand: true,
spacing: 2.5,
children: [
Widget.Button({
hexpand: true,
class_name: network.wifi.bind("enabled")
.as(enabled => enabled ? "management_button active" : "management_button"),
child: NetworkIndicator(),
on_primary_click: () => {
network.toggleWifi();
},
on_secondary_click: () => {
App.closeWindow(WINDOW_NAME);
App.openWindow("network");
}
}),
Widget.Button({
hexpand: true,
class_name: bluetooth_enabled.bind()
.as(state => state.trim() == "yes" ? "management_button active" : "management_button"
),
child: IconAndName({
label: "Bluetooth",
icon: "󰂯",
arrow: true,
}),
on_primary_click: () => {
Utils.execAsync(`${App.configDir}/scripts/bluetooth.sh --toggle`)
.then(out => { bluetooth_enabled.setValue(out) })
},
on_secondary_click: () => {
Utils.execAsync("blueman-manager")
App.closeWindow(WINDOW_NAME)
}
})
]
}),
Widget.Box({
orientation: Gtk.Orientation.HORIZONTAL,
spacing: 2.5,
hexpand: true,
children: [
Widget.Button({
hexpand: true,
class_name: idle_inhibitor.bind()
.as(bool => bool ? "management_button active" : "management_button"),
child: IconAndName({
label: "Idle inhibitor",
icon: ""
}),
onClicked: () => {
idle_inhibitor.setValue(!idle_inhibitor.value)
if (idle_inhibitor.value) Utils.execAsync(['bash', '-c', `pidof wayland-idle-inhibitor.py || ${App.configDir}/scripts/wayland-idle-inhibitor.py`]).catch(print)
else Utils.execAsync('pkill -f wayland-idle-inhibitor.py').catch(print);
},
setup: () => {
idle_inhibitor.setValue(!!Utils.exec('pidof wayland-idle-inhibitor.py'));
},
}),
Widget.Button({
hexpand: true,
class_name: notifications.bind("dnd").as((bool) => {
return bool ? "management_button active" : "management_button";
}),
on_clicked: () => {
notifications.dnd = notifications.dnd ? false : true
},
child: IconAndName({
label: "Do Not Disturb",
icon: ""
})
})
]
}),
Widget.Box({
orientation: Gtk.Orientation.HORIZONTAL,
spacing: 2.5,
hexpand: true,
children: [
Widget.Button({
hexpand: true,
class_name: screen_recorder.bind()
.as(bool => bool ? "management_button active" : "management_button"),
on_clicked: (self) => {
screen_recorder.setValue(!screen_recorder.value)
// Adjust indicator
let isRecording = isScreenRecordingOn()
self.toggleClassName("active-button", !isRecording) // Toggles active indicator
self.toggleClassName("recording", !isRecording) // Toggles active indicator
// Starts screen recorder if not running
// Stops screen recorder if running
Utils.execAsync(['bash', '-c', 'mkdir ~/Videos/Screenrecordings; pkill wf-recorder; if [ $? -ne 0 ]; then wf-recorder -f ~/Videos/Screenrecordings/recording_"$(date +\'%b-%d-%Y-%I:%M:%S-%P\')".mp4 -g "$(slurp)" --pixel-format yuv420p; fi']).catch(logError);
},
child: IconAndName({
label: "Screen Recorder",
icon: "",
setup: self=>{}
})
}),
Widget.Button({
hexpand: true,
class_name: night_light.bind()
.as(mode => mode ? "management_button active" : "management_button"),
child: IconAndName({
label: "Night Light",
icon: ""
}),
onClicked: () => {
night_light.setValue(!night_light.value)
Utils.execAsync(`${App.configDir}/scripts/night-light.sh --toggle`)
.then(out => {
if (out.trim() == "enabled") night_light.setValue(true)
else if (out.trim() == "disabled") night_light.setValue(false)
})
.catch(err => {
night_light.setValue(!night_light.value);
});
},
setup: () => {
Utils.execAsync(`${App.configDir}/scripts/night-light.sh --get`)
.then(out => {
if (out.trim() == "enabled") night_light.setValue(true)
else if (out.trim() == "disabled") night_light.setValue(false)
})
.catch(print);
}
})
]
})
]
})
}
function Page2() {
return Widget.Box({
orientation: Gtk.Orientation.VERTICAL,
spacing: 5,
hexpand: true,
class_name: "management_box",
children: [
Widget.Box({
orientation: Gtk.Orientation.HORIZONTAL,
spacing: 2.5,
children: [
Widget.Button({
hexpand: true,
class_name: "management_button",
child: IconAndName({
label: "Color picker",
icon: "",
arrow: true,
}),
on_primary_click: () => {
App.closeWindow(WINDOW_NAME);
Utils.execAsync("sleep 0.5")
.then(() =>
Utils.execAsync("hyprpicker -a").catch(print)
)
.catch(print);
}
}),
Widget.Button({
hexpand: true,
class_name: "management_button",
child: Widget.Box({
children: [
Widget.Label({
label: powerProfiles.bind('active_profile').as(x => icons[x])
}),
Widget.Label({
label: powerProfiles.bind('active_profile').as(x => x.toUpperCase())
})
]
}),
on_clicked: () => {
switch (powerProfiles.active_profile) {
case 'balanced':
powerProfiles.active_profile = 'performance';
break;
case 'power-saver':
powerProfiles.active_profile = 'balanced';
break;
default:
powerProfiles.active_profile = 'power-saver';
break;
};
},
}),
]
})
]
})
}
const createDotButton = (index: number) => Widget.Button({
label: '',
onClicked: () => currentPage.setValue(index),
class_name: currentPage.bind().as(v => v == index ? "dotbutton active" : "dotbutton"),
hexpand: false,
});
export function Management() {
let pages = {
"page1": Page1(),
"page2": Page2()
}
const numberOfPages = Object.keys(pages).length;
const pageNames = Array.from({ length: numberOfPages }, (_, i) => `page${i + 1}`);
const stack = Widget.Stack({
children: pages,
// @ts-ignore
shown: currentPage.bind().as(v => `page${v + 1}`),
transition: "slide_left_right",
transitionDuration: 200,
})
const dotButtons = pageNames.map((_, index) => createDotButton(index));
return Widget.EventBox({
onScrollUp: () => currentPage.setValue((currentPage.value + 1) % numberOfPages),
onScrollDown: () => {
if (currentPage.value - 1) {
currentPage.setValue(numberOfPages - 1)
return
}
currentPage.setValue(currentPage.value - 1);
},
child: Widget.Box({
orientation: Gtk.Orientation.VERTICAL,
children: [
stack,
Widget.Box({
children: dotButtons,
class_name: "dotbuttons_box",
halign: Gtk.Align.CENTER,
})
]
})
})
}
+88
View File
@@ -0,0 +1,88 @@
const { Gtk } = imports.gi;
import { NotificationsBox } from "./notifications.ts"
import { SystemBox } from "./system.ts"
let shown = Variable("Messages");
type ButtonType = {
page: string,
label: string,
icon: string
}
function Button({ page, label, icon }: ButtonType) {
return Widget.Button({
class_name: shown.bind().as(_page => _page == page ? "navigation_button active" : "navigation_button"),
hexpand: true,
child: Widget.Box({
orientation: Gtk.Orientation.VERTICAL,
class_name: "container_outer",
children: [
Widget.Box({
orientation: Gtk.Orientation.VERTICAL,
halign: Gtk.Align.CENTER,
class_name: "container",
child: Widget.Label({
hpack: "center",
label: icon,
class_name: "awesome_icon icon"
}),
}),
Widget.Label({
label: label,
class_name: "label",
})
]
}),
on_clicked: () => {
shown.setValue(page)
}
})
}
export function Navigation() {
const messages_apps = ["discord", "materialgram", "Telegram Desktop"]
let stack = Widget.Stack({
children: {
"Messages": NotificationsBox({ include: messages_apps }),
"Notifications": NotificationsBox({ exclude: messages_apps }),
"System": SystemBox()
},
transition: "crossfade",
transitionDuration: 200,
// @ts-ignore
shown: shown.bind()
})
const buttons = Widget.Box({
class_name: "navigation",
hexpand: true,
children: [
Button({
page: "Messages",
label: "Messages",
icon: ""
}),
Button({
page: "Notifications",
label: "Notifs",
icon: ""
}),
Button({
page: "System",
label: "System",
icon: ""
})
]
})
return Widget.Box({
vexpand: true,
orientation: Gtk.Orientation.VERTICAL,
class_name: "sidebar_bottom",
children: [
stack,
buttons
]
})
}
@@ -0,0 +1,183 @@
import { Notification as NotificationType } from "types/service/notifications";
const notifications = await Service.import("notifications")
const { Gtk, Gdk } = imports.gi;
function NotificationIcon({ app_entry, app_icon, image }: NotificationType) {
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),
})
}
function Notification(n: NotificationType) {
const icon = Widget.Box({
vpack: "start",
class_name: "notification_sidebar_icon",
child: NotificationIcon(n),
})
const title = Widget.Label({
class_name: "notification_sidebar_title",
wrap: true,
label: n.summary,
use_markup: true,
hpack: "start",
xalign: 0
})
const body = Widget.Label({
class_name: "notification_sidebar_body",
label: n.body,
wrap: true,
use_markup: true,
hpack: "start",
xalign: 0
})
const actions = Widget.Box({
halign: Gtk.Align.END,
class_name: "notification_sidebar_actions",
children: n.actions.map(({ id, label }) => Widget.Button({
class_name: "notification_sidebar_button",
on_clicked: () => {
n.invoke(id)
},
hexpand: false,
child: Widget.Label(label),
})),
})
let Box = Widget.EventBox(
{
attribute: { id: n.id },
on_secondary_click: () => n.close(),
},
Widget.Box(
{
class_name: `notification_sidebar ${n.urgency}`,
vertical: true,
},
Widget.Box([
icon,
Widget.Box(
{
vertical: true,
hexpand: true,
},
title,
body,
),
]),
actions,
),
)
return Box
}
type NotificationsBoxType = {
exclude?: string[],
include?: string[]
}
export function NotificationsBox({ exclude = [], include = [] }: NotificationsBoxType) {
let isEmpty = Variable(false)
const list = Widget.Box({
vertical: true,
children: notifications.popups.map(Notification)
})
const _list = Widget.Box({
vertical: true,
children: [
list,
Widget.Label({
label: "Empty",
visible: isEmpty.bind(),
class_name: "empty"
})
],
class_name: "notifications_list_sidebar"
})
function setIsEmpty() {
if (list.children.length > 0) isEmpty.setValue(false)
else isEmpty.setValue(true)
}
function onNotified(_: any, id: number) {
const n = notifications.getNotification(id)
if (n) {
if (exclude.includes(n.app_name.trim()))
return
if (include.length == 0 || include.includes(n.app_name.trim())) {
const notificationWidget = Notification(n)
list.children = [notificationWidget, ...list.children]
setIsEmpty()
}
}
}
function onClosed(_: any, id: number) {
list.children.find(n => n.attribute.id === id)?.destroy()
setIsEmpty()
}
list.hook(notifications, onNotified, "notified")
.hook(notifications, onClosed, "closed")
const menu = Widget.Menu({
class_name: "notifications_menu",
children: [
Widget.MenuItem({
child: Widget.Label('Close all'),
class_name: "notifications_menu_item",
on_activate: () => {
for (let x of list.children) {
const n = notifications.getNotification(x.attribute.id)
if (n) {
n.dismiss()
n.close()
}
}
}
}),
],
})
return Widget.EventBox({
vexpand: true,
hexpand: true,
on_secondary_click: (_, event) => {
menu.popup_at_pointer(event)
},
child: Widget.Scrollable({
class_name: "notifications_sidebar_scrollable",
hscroll: "never",
child: _list,
hexpand: true
/** this is a simple one liner that could be used instead of
hooking into the 'notified' and 'dismissed' signals.
but its not very optimized becuase it will recreate
the whole list everytime a notification is added or dismissed */
// children: notifications.bind('popups')
// .as(popups => popups.map(Notification))
}),
})
}
@@ -0,0 +1,76 @@
import { Notification as NotificationType } from "types/service/notifications";
import { NotificationPopups, Notification } from "modules/notificationPopups";
import Box from "types/widgets/box";
const notifications = await Service.import("notifications")
const { Gtk, Gdk } = imports.gi;
type NotificationsBoxType = {
exclude?: string[],
include?: string[]
}
const NotificationNoReveal = (notification: NotificationType, visible = false, dismiss = true) => {
type BoxAttrs = {
destroyWithAnims: any,
count: number,
id: number,
}
const _notification = Notification(notification, dismiss);
let box: Box<any, BoxAttrs>;
const destroyWithAnims = () => {
box.destroy();
};
box = Widget.Box({
hexpand: true,
attribute: {
"destroyWithAnims": destroyWithAnims,
count: 0,
id: notification.id,
},
children: [_notification],
});
return box;
};
export function NotificationsBox({ exclude = [], include = [] }: NotificationsBoxType) {
const popups = NotificationPopups(
false, { exclude: exclude, include: include },
false, NotificationNoReveal
);
const menu = Widget.Menu({
class_name: "notifications_menu",
children: [
Widget.MenuItem({
child: Widget.Label('Close all'),
class_name: "notifications_menu_item",
on_activate: () => {
for (let n of notifications.notifications) {
if (n) {
n.dismiss()
n.close()
}
}
}
}),
],
})
return Widget.EventBox({
vexpand: true,
hexpand: true,
on_secondary_click: (_, event) => {
menu.popup_at_pointer(event)
},
child: Widget.Scrollable({
class_name: "notifications_sidebar_scrollable",
hscroll: "never",
child: popups,
hexpand: true
}),
})
}
+18
View File
@@ -0,0 +1,18 @@
import { Management } from "./management.ts"
import { Buttons } from "./buttons.ts";
import { Navigation } from "./navigation.ts";
const { Gtk } = imports.gi;
export function SideBar() {
return Widget.Box({
orientation: Gtk.Orientation.VERTICAL,
class_name: "sidebar_main_box",
hexpand: true,
children: [
Management(),
Buttons(),
Navigation()
// NotificationsBox()
]
})
}
+210
View File
@@ -0,0 +1,210 @@
import { Variable as VariableType } from "types/variable";
const { Gtk } = imports.gi;
const cpu_name = await Utils.execAsync(`${App.configDir}/scripts/system.sh --cpu-name`);
const cpu_cores = await Utils.execAsync(`${App.configDir}/scripts/system.sh --cpu-cores`);
const amount_of_ram = await Utils.execAsync(`${App.configDir}/scripts/system.sh --ram`);
const kernel_name = await Utils.execAsync(`${App.configDir}/scripts/system.sh --kernel`);
type InfoType = {
cpu: string,
ram: string,
swap: string,
cpu_temp: string
}
async function SystemInfo(): Promise<InfoType> {
const cpu_usage = await Utils.execAsync(`${App.configDir}/scripts/system.sh --cpu-usage`)
.then(str => String(str))
.catch(err => {
print(err)
return "0"
});
const ram_usage = await Utils.execAsync(`${App.configDir}/scripts/system.sh --ram-usage`)
.then(str => String(str))
.catch(err => {
print(err)
return "0"
});
const swap_usage = await Utils.execAsync(`${App.configDir}/scripts/system.sh --swap-usage`)
.then(str => String(str))
.catch(err => {
print(err)
return "0"
});
const cpu_temp = await Utils.execAsync(`${App.configDir}/scripts/system.sh --cpu-temp`)
.then(str => String(str))
.catch(err => {
print(err)
return "0"
});
return {
cpu: cpu_usage,
ram: ram_usage,
swap: swap_usage,
cpu_temp: cpu_temp
}
}
const usage = Variable({
cpu: "0",
ram: "0",
swap: "0",
cpu_temp: "0"
} as unknown as Promise<InfoType>, {
poll: [1000, async () => {
const result = await SystemInfo()
.then(result => result)
return result
}]
})
function checkBacklight() {
const get = Utils.execAsync(`${App.configDir}/scripts/backlight.sh --get`)
.then(out => Number(out.trim()))
.catch(print);
return get
}
function checkBrightness() {
const get = Utils.execAsync(`${App.configDir}/scripts/brightness.sh --get`)
.then(out => Number(out.trim()))
.catch(print);
return get
}
const current_backlight = Variable(100, {
poll: [500, checkBacklight]
})
const current_brightness = Variable(100, {
poll: [500, checkBrightness]
})
const Usage = (
name: string, var_name: keyof InfoType,
class_name: string | undefined
) => {
const usage_progress_bar = Widget.ProgressBar({
class_name: "usage_bar",
value: usage.bind().as(usage => Number(usage[var_name]) / 100),
})
const usage_overlay = Widget.Overlay({
child: usage_progress_bar,
overlay: Widget.Box({
halign: Gtk.Align.END,
valign: Gtk.Align.CENTER,
class_name: "usage_bar_point"
})
})
const _usage = Widget.Box({
class_name: `${class_name} usage_box`,
orientation: Gtk.Orientation.VERTICAL,
children: [
Widget.Label({
label: usage.bind().as(usage => `${name}: ${usage[var_name]}%`),
hpack: "start",
vpack: "center"
}),
usage_overlay
]
})
return _usage
}
const InfoLabel = (name: string, var_name: keyof InfoType, end: string) => {
return Widget.Label({
class_name: "info_label",
truncate: "end",
hpack: "start",
label: usage.bind().as(usage => `${name}: ${usage[var_name]}${end}`)
})
}
const InfoLabelString = (name: string, value: string, end: string) => {
return Widget.Label({
class_name: "info_label",
truncate: "end",
hpack: "start",
label: `${name}: ${value}${end}`
})
}
export function SystemBox() {
const backlight = Widget.Slider({
min: 0,
max: 100,
draw_value: false,
class_name: "system_scale backlight",
// @ts-ignore
value: current_backlight.bind(),
on_change: self => {
current_backlight.setValue(Number(self.value));
Utils.execAsync(`${App.configDir}/scripts/backlight.sh --smooth ${self.value}`)
.catch(print);
},
tooltip_markup: "Backlight"
})
const brightness = Widget.Slider({
min: 0,
max: 1,
draw_value: false,
class_name: "system_scale brightness",
// @ts-ignore
value: current_brightness.bind(),
on_change: self => {
current_brightness.setValue(Number(self.value));
Utils.execAsync(`${App.configDir}/scripts/brightness.sh --set ${self.value}`)
.catch(print);
},
tooltip_markup: "Brightness"
})
const slider_box = Widget.Box({
orientation: Gtk.Orientation.VERTICAL,
class_name: "slider_box",
children: [
backlight,
brightness
]
})
const usage_box = Widget.Box({
orientation: Gtk.Orientation.VERTICAL,
class_name: "usage_box",
spacing: 0,
children: [
Usage("CPU", "cpu", "cpu_usage"),
Usage("RAM", "ram", "ram_usage"),
Usage("SWAP", "swap", "swap_usage")
]
})
const info_box = Widget.Box({
orientation: Gtk.Orientation.VERTICAL,
class_name: "info_box",
spacing: 0,
children: [
InfoLabel("CPU temp", "cpu_temp", "°C"),
InfoLabelString("CPU name", cpu_name, ""),
InfoLabelString("CPU cores", cpu_cores, ""),
InfoLabelString("RAM amount", amount_of_ram, ""),
InfoLabelString("Kernel", kernel_name, "")
],
vexpand: true
})
return Widget.Scrollable({
child: Widget.Box({
orientation: Gtk.Orientation.VERTICAL,
class_name: "system_box",
spacing: 10,
children: [
slider_box,
usage_box,
info_box
]
})
})
}