ags steal

This commit is contained in:
2023-10-29 15:05:17 +01:00
parent e7416f5bdf
commit 912479c43d
101 changed files with 10639 additions and 1 deletions

View File

@@ -0,0 +1,364 @@
const { Gio, Gdk, Gtk } = imports.gi;
import { App, Widget, Utils } from '../imports.js';
const { Box, CenterBox, Label, Button } = Widget;
import { MaterialIcon } from "./lib/materialicon.js";
import { getCalendarLayout } from "../scripts/calendarlayout.js";
import Todo from "../scripts/todo.js";
import { setupCursorHover } from "./lib/cursorhover.js";
import { NavigationIndicator } from "./lib/navigationindicator.js";
let calendarJson = getCalendarLayout(undefined, true);
let monthshift = 0;
function fileExists(filePath) {
let file = Gio.File.new_for_path(filePath);
return file.query_exists(null);
}
function getDateInXMonthsTime(x) {
var currentDate = new Date(); // Get the current date
var targetMonth = currentDate.getMonth() + x; // Calculate the target month
var targetYear = currentDate.getFullYear(); // Get the current year
// Adjust the year and month if necessary
targetYear += Math.floor(targetMonth / 12);
targetMonth = (targetMonth % 12 + 12) % 12;
// Create a new date object with the target year and month
var targetDate = new Date(targetYear, targetMonth, 1);
// Set the day to the last day of the month to get the desired date
// targetDate.setDate(0);
return targetDate;
}
const weekDays = [ // stupid stupid stupid!! how tf is Sunday the first day of the week??
{ day: 'Su', today: 0 },
{ day: 'Mo', today: 0 },
{ day: 'Tu', today: 0 },
{ day: 'We', today: 0 },
{ day: 'Th', today: 0 },
{ day: 'Fr', today: 0 },
{ day: 'Sa', today: 0 },
]
const CalendarDay = (day, today) => Widget.Button({
className: `sidebar-calendar-btn ${today == 1 ? 'sidebar-calendar-btn-today' : (today == -1 ? 'sidebar-calendar-btn-othermonth' : '')}`,
child: Widget.Overlay({
child: Box({}),
overlays: [Label({
halign: 'center',
className: 'txt-smallie txt-semibold sidebar-calendar-btn-txt',
label: String(day),
})],
})
})
const CalendarWidget = () => {
const calendarMonthYear = Widget.Button({
className: 'txt txt-large sidebar-calendar-monthyear-btn',
onClicked: () => shiftCalendarXMonths(0),
setup: (button) => {
button.label = `${new Date().toLocaleString('default', { month: 'long' })} ${new Date().getFullYear()}`;
setupCursorHover(button);
}
});
const addCalendarChildren = (box, calendarJson) => {
box.children = calendarJson.map((row, i) => Widget.Box({
// homogeneous: true,
className: 'spacing-h-5',
children: row.map((day, i) =>
CalendarDay(day.day, day.today)
)
}))
}
function shiftCalendarXMonths(x) {
if (x == 0)
monthshift = 0;
else
monthshift += x;
var newDate = undefined;
if (monthshift == 0)
newDate = new Date();
else
newDate = getDateInXMonthsTime(monthshift);
calendarJson = getCalendarLayout(newDate, monthshift == 0);
calendarMonthYear.label = `${monthshift == 0 ? '' : '• '}${newDate.toLocaleString('default', { month: 'long' })} ${newDate.getFullYear()}`;
addCalendarChildren(calendarDays, calendarJson);
}
const calendarHeader = Widget.Box({
className: 'spacing-h-5 sidebar-calendar-header',
setup: (box) => {
box.pack_start(calendarMonthYear, false, false, 0);
box.pack_end(Widget.Box({
className: 'spacing-h-5',
children: [
Button({
className: 'sidebar-calendar-monthshift-btn',
onClicked: () => shiftCalendarXMonths(-1),
child: MaterialIcon('chevron_left', 'norm'),
setup: (button) => setupCursorHover(button),
}),
Button({
className: 'sidebar-calendar-monthshift-btn',
onClicked: () => shiftCalendarXMonths(1),
child: MaterialIcon('chevron_right', 'norm'),
setup: (button) => setupCursorHover(button),
})
]
}), false, false, 0);
}
})
const calendarDays = Widget.Box({
hexpand: true,
vertical: true,
className: 'spacing-v-5',
setup: (box) => {
addCalendarChildren(box, calendarJson);
}
});
return Widget.EventBox({
onScrollUp: () => shiftCalendarXMonths(-1),
onScrollDown: () => shiftCalendarXMonths(1),
child: Widget.Box({
halign: 'center',
children: [
Widget.Box({
hexpand: true,
vertical: true,
className: 'spacing-v-5',
children: [
calendarHeader,
Widget.Box({
homogeneous: true,
className: 'spacing-h-5',
children: weekDays.map((day, i) => CalendarDay(day.day, day.today))
}),
calendarDays,
]
})
]
})
});
};
const defaultTodoSelected = 'undone';
const todoItems = (isDone) => Widget.Scrollable({
child: Widget.Box({
vertical: true,
connections: [[Todo, (self) => {
self.children = Todo.todo_json.map((task, i) => {
if (task.done != isDone) return null;
return Widget.Box({
className: 'spacing-h-5',
children: [
Widget.Label({
className: 'txt txt-small',
label: '•',
}),
Widget.Label({
hexpand: true,
xalign: 0,
wrap: true,
className: 'txt txt-small sidebar-todo-txt',
label: task.content,
}),
Widget.Button({
valign: 'center',
className: 'txt sidebar-todo-item-action',
child: MaterialIcon(`${isDone ? 'remove_done' : 'check'}`, 'norm', { valign: 'center' }),
onClicked: () => {
if (isDone)
Todo.uncheck(i);
else
Todo.check(i);
},
setup: (button) => setupCursorHover(button),
}),
Widget.Button({
valign: 'center',
className: 'txt sidebar-todo-item-action',
child: MaterialIcon('delete_forever', 'norm', { valign: 'center' }),
onClicked: () => {
Todo.remove(i);
},
setup: (button) => setupCursorHover(button),
}),
]
});
})
if (self.children.length == 0) {
self.homogeneous = true;
self.children = [
Widget.Box({
hexpand: true,
vertical: true,
valign: 'center',
className: 'txt',
children: [
MaterialIcon(`${isDone ? 'checklist' : 'check_circle'}`, 'badonkers'),
Label({ label: `${isDone ? 'Finished tasks will go here' : 'Nothing here!'}` })
]
})
]
}
else self.homogeneous = false;
}, 'updated']]
})
});
const todoItemsBox = Widget.Stack({
valign: 'fill',
transition: 'slide_left_right',
items: [
['undone', todoItems(false)],
['done', todoItems(true)],
],
});
const TodoWidget = () => {
const TodoTabButton = (isDone, navIndex) => Widget.Button({
hexpand: true,
className: 'sidebar-todo-selector-tab',
onClicked: (button) => {
todoItemsBox.shown = `${isDone ? 'done' : 'undone'}`;
const kids = button.get_parent().get_children();
for (let i = 0; i < kids.length; i++) {
if (kids[i] != button) kids[i].toggleClassName('sidebar-todo-selector-tab-active', false);
else button.toggleClassName('sidebar-todo-selector-tab-active', true);
}
// Fancy highlighter line width
const buttonWidth = button.get_allocated_width();
const highlightWidth = button.get_children()[0].get_allocated_width();
navIndicator.style = `
font-size: ${navIndex}px;
padding: 0px ${(buttonWidth - highlightWidth) / 2}px;
`;
},
child: Box({
halign: 'center',
className: 'spacing-h-5',
children: [
MaterialIcon(`${isDone ? 'task_alt' : 'format_list_bulleted'}`, 'larger'),
Label({
className: 'txt txt-smallie',
label: `${isDone ? 'Done' : 'Unfinished'}`,
})
]
}),
setup: (button) => {
button.toggleClassName('sidebar-todo-selector-tab-active', defaultTodoSelected === `${isDone ? 'done' : 'undone'}`);
setupCursorHover(button);
},
});
const undoneButton = TodoTabButton(false, 0);
const doneButton = TodoTabButton(true, 1);
const navIndicator = NavigationIndicator(2, false, {
className: 'sidebar-todo-selector-highlight',
style: 'font-size: 0px;',
setup: (self) => {
// Fancy highlighter line width
const buttonWidth = undoneButton.get_allocated_width();
const highlightWidth = undoneButton.get_children()[0].get_allocated_width();
navIndicator.style = `
font-size: ${navIndex}px;
padding: 0px ${(buttonWidth - highlightWidth) / 2}px;
`;
}
})
return Widget.Box({
hexpand: true,
vertical: true,
className: 'spacing-v-10',
setup: (box) => {
// undone/done selector rail
box.pack_start(Widget.Box({
vertical: true,
children: [
Widget.Box({
className: 'sidebar-todo-selectors spacing-h-5',
homogeneous: true,
setup: (box) => {
box.pack_start(undoneButton, false, true, 0);
box.pack_start(doneButton, false, true, 0);
}
}),
Widget.Box({
className: 'sidebar-todo-selector-highlight-offset',
homogeneous: true,
children: [navIndicator]
})
]
}), false, false, 0);
box.pack_end(todoItemsBox, true, true, 0);
}
});
};
const defaultShown = 'calendar';
const contentStack = Widget.Stack({
hexpand: true,
items: [
['calendar', CalendarWidget()],
['todo', TodoWidget()],
// ['stars', Widget.Label({ label: 'GitHub feed will be here' })],
],
transition: 'slide_up_down',
transitionDuration: 180,
setup: (stack) => {
stack.shown = defaultShown;
}
})
const StackButton = (stackItemName, icon, name) => Widget.Button({
className: 'button-minsize sidebar-navrail-btn sidebar-button-alone txt-small spacing-h-5',
onClicked: (button) => {
contentStack.shown = stackItemName;
const kids = button.get_parent().get_children();
for (let i = 0; i < kids.length; i++) {
if (kids[i] != button) kids[i].toggleClassName('sidebar-navrail-btn-active', false);
else button.toggleClassName('sidebar-navrail-btn-active', true);
}
},
child: Box({
className: 'spacing-v-5',
vertical: true,
children: [
Label({
className: `txt icon-material txt-hugeass`,
label: icon,
}),
Label({
label: name,
className: 'txt txt-smallie',
}),
]
}),
setup: (button) => {
button.toggleClassName('sidebar-navrail-btn-active', defaultShown === stackItemName);
setupCursorHover(button);
}
});
export const ModuleCalendar = () => Box({
className: 'sidebar-group spacing-h-5',
setup: (box) => {
box.pack_start(Box({
valign: 'center',
homogeneous: true,
vertical: true,
className: 'sidebar-navrail spacing-v-10',
children: [
StackButton('calendar', 'calendar_month', 'Calendar'),
StackButton('todo', 'lists', 'To Do'),
// StackButton(box, 'stars', 'star', 'GitHub'),
]
}), false, false, 0);
// ags.Widget({ // TDOO: replace this sad default calendar with a custom one
// type: imports.gi.Gtk.Calendar,
// }),
box.pack_end(contentStack, false, false, 0);
}
})

View File

@@ -0,0 +1,60 @@
import { Widget } from '../imports.js';
import { keybindList } from "../data/keybinds.js";
export const Keybinds = () => Widget.Box({
vertical: false,
className: "spacing-h-15",
homogeneous: true,
children: keybindList.map((group, i) => Widget.Box({ // Columns
vertical: true,
className: "spacing-v-15",
children: group.map((category, i) => Widget.Box({ // Categories
vertical: true,
className: "spacing-v-15",
children: [
Widget.Box({ // Category header
vertical: false,
className: "spacing-h-10",
children: [
Widget.Label({
xalign: 0,
className: "icon-material txt txt-larger",
label: category.icon,
}),
Widget.Label({
xalign: 0,
className: "cheatsheet-category-title txt",
label: category.name,
}),
]
}),
Widget.Box({
vertical: false,
className: "spacing-h-10",
children: [
Widget.Box({ // Keys
vertical: true,
homogeneous: true,
children: category.binds.map((keybinds, i) => Widget.Box({ // Binds
vertical: false,
children: keybinds.keys.map((key, i) => Widget.Label({ // Specific keys
className: `${key == 'OR' || key == '+' ? 'cheatsheet-key-notkey' : 'cheatsheet-key'} txt-small`,
label: key,
}))
}))
}),
Widget.Box({ // Actions
vertical: true,
homogeneous: true,
children: category.binds.map((keybinds, i) => Widget.Label({ // Binds
xalign: 0,
label: keybinds.action,
className: "txt chearsheet-action txt-small",
}))
})
]
})
]
}))
})),
});

View File

@@ -0,0 +1,101 @@
import { App, Service, Utils, Widget } from '../imports.js';
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
const { CONFIG_DIR, exec, execAsync } = Utils;
import { deflisten } from '../scripts/scripts.js';
import { setupCursorHover } from "./lib/cursorhover.js";
import { RoundedCorner } from "./lib/roundedcorner.js";
import Brightness from '../scripts/brightness.js';
import Indicator from '../scripts/indicator.js';
// Removes everything after the last
// em dash, en dash, minus, vertical bar, or middle dot (note: maybe add open parenthesis?)
// For example:
// • Discord | #ricing-theming | r/unixporn — Mozilla Firefox --> • Discord | #ricing-theming
// GJS Error · Issue #112 · Aylur/ags — Mozilla Firefox --> GJS Error · Issue #112
function truncateTitle(str) {
let lastDash = -1;
let found = -1; // 0: em dash, 1: en dash, 2: minus, 3: vertical bar, 4: middle dot
for (let i = str.length - 1; i >= 0; i--) {
if (str[i] === '—') {
found = 0;
lastDash = i;
}
else if (str[i] === '' && found < 1) {
found = 1;
lastDash = i;
}
else if (str[i] === '-' && found < 2) {
found = 2;
lastDash = i;
}
else if (str[i] === '|' && found < 3) {
found = 3;
lastDash = i;
}
else if (str[i] === '·' && found < 4) {
found = 4;
lastDash = i;
}
}
if (lastDash === -1) return str;
return str.substring(0, lastDash);
}
export const ModuleLeftSpace = () => Widget.EventBox({
onScrollUp: () => {
Indicator.popup(1); // Since the brightness and speaker are both on the same window
Brightness.screen_value += 0.05;
},
onScrollDown: () => {
Indicator.popup(1); // Since the brightness and speaker are both on the same window
Brightness.screen_value -= 0.05;
},
child: Widget.Box({
homogeneous: false,
children: [
RoundedCorner('topleft', { className: 'corner-black' }),
Widget.Overlay({
overlays: [
Widget.Box({ hexpand: true }),
Widget.Box({
className: 'bar-sidemodule', hexpand: true,
children: [Widget.Button({
className: 'bar-space-button',
child: Widget.Box({
vertical: true,
children: [
Widget.Scrollable({
hexpand: true, vexpand: true,
hscroll: 'automatic', vscroll: 'never',
child: Widget.Box({
vertical: true,
children: [
Widget.Label({
xalign: 0,
className: 'txt txt-smaller bar-topdesc',
connections: [[Hyprland, label => { // Hyprland.active.client
label.label = Hyprland.active.client._class.length === 0 ? 'Desktop' : Hyprland.active.client._class;
}]],
}),
Widget.Label({
xalign: 0,
className: 'txt txt-smallie',
connections: [
[Hyprland, label => { // Hyprland.active.client
label.label = Hyprland.active.client._title.length === 0 ? `Workspace ${Hyprland.active.workspace.id}` : truncateTitle(Hyprland.active.client._title);
}]
],
})
]
})
})
]
}),
setup: (button) => setupCursorHover(button),
})]
}),
]
})
]
})
});

View File

@@ -0,0 +1,245 @@
// Not yet used. For cool drag and drop stuff. Thanks DevAlien
const Toggles = {};
Toggles.Wifi = NetworkToggle;
Toggles.Bluetooth = BluetoothToggle;
Toggles.DND = DNDToggle;
Toggles.ThemeToggle = ThemeToggle;
Toggles.ProfileToggle = ProfileToggle;
// Toggles.Record = RecordToggle;
// Toggles.Airplane = AirplaneToggle;
// Toggles.DoNotDisturb = DoNotDisturbToggle;
const TARGET = [Gtk.TargetEntry.new("text/plain", Gtk.TargetFlags.SAME_APP, 0)];
export class ActionCenter extends Gtk.Box {
static {
GObject.registerClass({
GTypeName: 'ActionCenter',
Properties: {
},
}, this);
}
constructor({ className = "ActionCenter", toggles, ...rest }) {
super(rest);
this.toggles = Toggles
this.currentToggles = Settings.getSetting("toggles", []);
this.mainFlowBox = this._setupFlowBox(className + QSView.editing && className + "Editing");
this.mainFlowBox.connect("drag_motion", this._dragMotionMain);
this.mainFlowBox.connect("drag_drop", this._dragDropMain);
this._dragged = {};
this._draggedExtra = {};
this._dragged;
this._currentPosition = 0;
this._orderedState;
this._draggedName;
this.updateList(toggles, this.mainFlowBox)
this.set_orientation(Gtk.Orientation.VERTICAL);
this.add(this.mainFlowBox)
this.mainFlowBox.set_size_request(1, 30)
if (QSView.editing) {
this.extraFlowBox = this._setupFlowBox(className);
this.extraFlowBox.connect("drag_motion", this._dragMotionExtra);
this.extraFlowBox.connect("drag_drop", this._dragDropExtra);
this.updateList(this._getExtraToggles(), this.extraFlowBox)
this.add(Box({
vertical: true,
children: [
Label("Extra widgets"),
Label("Drop here to remove or drag from here to add"),
this.extraFlowBox
]
}))
}
}
_getExtraToggles() {
let toggles = { ...this.toggles }
this.currentToggles.map(t => {
if (toggles[t]) {
delete toggles[t];
}
});
return Object.keys(toggles);
}
_setupFlowBox(className) {
const flowBox = new Gtk.FlowBox();
flowBox.set_valign(Gtk.Align.FILL);
flowBox.set_min_children_per_line(2);
flowBox.set_max_children_per_line(2);
flowBox.set_selection_mode(Gtk.SelectionMode.NONE);
flowBox.get_style_context().add_class(className);
flowBox.set_homogeneous(true);
flowBox.drag_dest_set(Gtk.DestDefaults.ALL, TARGET, Gdk.DragAction.COPY);
return flowBox;
}
createWidget = (name, index, type) => {
const editSetup = (widget) => {
widget.drag_source_set(
Gdk.ModifierType.BUTTON1_MASK,
TARGET,
Gdk.DragAction.COPY
);
widget.connect("drag-begin", (w, context) => {
const widgetContainer = widget.get_parent();
Gtk.drag_set_icon_surface(context, createSurfaceFromWidget(widgetContainer));
this._dragged = {
widget: widgetContainer.get_parent().get_parent(),
container: widgetContainer,
name: name,
currentPosition: type === "Main" ? index : null,
currentPositionExtra: type === "Extra" ? index : null,
from: type,
}
widgetContainer.get_style_context().add_class("hidden");
if (type !== "Main") {
this.extraFlowBox.remove(this._dragged.widget);
}
return true;
});
widget.connect("drag-failed", () => {
this.updateList(Settings.getSetting("toggles"), this.mainFlowBox)
this.updateList(this._getExtraToggles(), this.extraFlowBox)
});
}
let row = new Gtk.FlowBoxChild({ visible: true });
row.add(Toggles[name]({ setup: QSView.editing && editSetup, QSView: QSView }));
row._index = index;
row._name = name;
return row;
}
updateList(toggles, flowBox) {
let type = flowBox === this.mainFlowBox ? "Main" : "Extra"
var childrenBox = flowBox.get_children();
childrenBox.forEach((element) => {
flowBox.remove(element);
element.destroy();
});
if (!toggles) return;
toggles.forEach((name, i) => {
if (Toggles[name])
flowBox.add(this.createWidget(name, i, type));
});
flowBox.show_all();
}
_dragMotionMain = (widget, context, x, y, time) => {
if (this._dragged.currentPositionExtra !== null) {
this._dragged.currentPositionExtra = null;
if (this._isChild(this.extraFlowBox, this._dragged.widget)) {
this.extraFlowBox.remove(this._dragged.widget);
}
}
const children = this.mainFlowBox.get_children();
const sampleItem = children[0];
const sampleWidth = sampleItem.get_allocation().width;
const sampleHeight = sampleItem.get_allocated_height();
const perLine = Math.floor(this.mainFlowBox.get_allocation().width / sampleWidth);
const pos = Math.floor(y / sampleHeight) * perLine + Math.floor(x / sampleWidth);
if (pos >= children.length && pos !== 0) return false;
if (this._dragged.currentPosition === null) {
this.mainFlowBox.insert(this._dragged.widget, pos);
this._dragged.currentPosition = pos;
} else if (this._dragged.currentPosition !== pos) {
if (this._isChild(this.mainFlowBox, this._dragged.widget)) {
this.mainFlowBox.remove(this._dragged.widget);
}
this.mainFlowBox.insert(this._dragged.widget, pos);
this._dragged.currentPosition = pos;
}
return true;
}
_dragDropMain = () => {
if (this._dragged.from !== "Main") {
this.currentToggles.splice(this._dragged.currentPosition, 0, this._dragged.name);
} else {
const indexCurrentToggle = this.currentToggles.indexOf(this._dragged.name);
this.currentToggles.splice(indexCurrentToggle, 1);
this.currentToggles.splice(this._dragged.currentPosition, 0, this._dragged.name);
}
Settings.setSetting("toggles", this.currentToggles);
this._dragged.container.get_style_context().remove_class("hidden");
return true;
}
_dragDropExtra = () => {
if (this._dragged.from === "Main") {
const indexCurrentToggle = this.currentToggles.indexOf(this._dragged.name);
this.currentToggles.splice(indexCurrentToggle, 1);
}
Settings.setSetting("toggles", this.currentToggles);
this._dragged.container.get_style_context().remove_class("hidden");
return true;
}
_dragMotionExtra = (widget, context, x, y, time) => {
if (this._dragged.currentPosition !== null) {
this._dragged.currentPosition = null;
if (this._isChild(this.mainFlowBox, this._dragged.widget)) {
this.mainFlowBox.remove(this._dragged.widget);
}
}
const children = this.extraFlowBox.get_children();
const sampleItem = children[0];
let pos = 0;
if (sampleItem) {
const sampleWidth = sampleItem.get_allocation().width;
const sampleHeight = sampleItem.get_allocated_height();
const perLine = Math.floor(this.extraFlowBox.get_allocation().width / sampleWidth);
pos = Math.floor(y / sampleHeight) * perLine + Math.floor(x / sampleWidth);
}
if (pos >= children.length && pos !== 0) return false;
if (this._dragged.currentPositionExtra === null) {
this.extraFlowBox.insert(this._dragged.widget, pos);
this._dragged.currentPositionExtra = pos;
}
if (this._dragged.currentPositionExtra !== pos) {
if (this._isChild(this.extraFlowBox, this._dragged.widget)) {
this.extraFlowBox.remove(this._dragged.widget);
}
this.extraFlowBox.insert(this._dragged.widget, pos);
this._dragged.currentPositionExtra = pos;
}
return true;
}
_isChild(container, widget) {
let found = false;
container.get_children().forEach((c) => {
if (c === widget) found = true;
})
return found;
}
}

View File

@@ -0,0 +1,74 @@
const { Gdk, Gtk } = imports.gi;
const GObject = imports.gi.GObject;
const Lang = imports.lang;
import { Utils, Widget } from '../../imports.js';
// min-height for diameter
// min-width for trough stroke
// padding for space between trough and progress
// margin for space between widget and parent
// background-color for trough color
// color for progress color
// font size for progress value (0-100px) (hacky i know, but i want animations)
// TODO: border-radius for rounded ends maybe (unimportant)
export const AnimatedCircProg = (props) => Widget({
...props,
type: Gtk.DrawingArea,
setup: (area) => {
const styleContext = area.get_style_context();
const width = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
const height = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
const padding = styleContext.get_padding(Gtk.StateFlags.NORMAL).left;
const marginLeft = styleContext.get_margin(Gtk.StateFlags.NORMAL).left;
const marginRight = styleContext.get_margin(Gtk.StateFlags.NORMAL).right;
const marginTop = styleContext.get_margin(Gtk.StateFlags.NORMAL).top;
const marginBottom = styleContext.get_margin(Gtk.StateFlags.NORMAL).bottom;
area.set_size_request(width + marginLeft + marginRight, height + marginTop + marginBottom);
area.connect('draw', Lang.bind(area, (area, cr) => {
const styleContext = area.get_style_context();
const width = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
const height = styleContext.get_property('min-height', Gtk.StateFlags.NORMAL);
const padding = styleContext.get_padding(Gtk.StateFlags.NORMAL).left;
const marginLeft = styleContext.get_margin(Gtk.StateFlags.NORMAL).left;
const marginRight = styleContext.get_margin(Gtk.StateFlags.NORMAL).right;
const marginTop = styleContext.get_margin(Gtk.StateFlags.NORMAL).top;
const marginBottom = styleContext.get_margin(Gtk.StateFlags.NORMAL).bottom;
area.set_size_request(width + marginLeft + marginRight, height + marginTop + marginBottom);
const progressValue = styleContext.get_property('font-size', Gtk.StateFlags.NORMAL) / 100.0;
const bg_stroke = styleContext.get_property('min-width', Gtk.StateFlags.NORMAL);
const fg_stroke = bg_stroke - padding;
const radius = Math.min(width, height) / 2.0 - Math.max(bg_stroke, fg_stroke) / 2.0;
const center_x = width / 2.0 + marginLeft;
const center_y = height / 2.0 + marginTop;
const start_angle = -Math.PI / 2.0;
const end_angle = start_angle + (2 * Math.PI * progressValue);
const start_x = center_x + Math.cos(start_angle) * radius;
const start_y = center_y + Math.sin(start_angle) * radius;
const end_x = center_x + Math.cos(end_angle) * radius;
const end_y = center_y + Math.sin(end_angle) * radius;
// Draw background
const background_color = styleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
cr.setSourceRGBA(background_color.red, background_color.green, background_color.blue, background_color.alpha);
cr.arc(center_x, center_y, radius, 0, 2 * Math.PI);
cr.setLineWidth(bg_stroke);
cr.stroke();
// Draw progress
const color = styleContext.get_property('color', Gtk.StateFlags.NORMAL);
cr.setSourceRGBA(color.red, color.green, color.blue, color.alpha);
cr.arc(center_x, center_y, radius, start_angle, end_angle);
cr.setLineWidth(fg_stroke);
cr.stroke();
// Draw rounded ends for progress arcs
cr.setLineWidth(0);
cr.arc(start_x, start_y, fg_stroke / 2, 0, 0 - 0.01);
cr.fill();
cr.arc(end_x, end_y, fg_stroke / 2, 0, 0 - 0.01);
cr.fill();
}))
},
})

View File

@@ -0,0 +1,10 @@
const { Gdk, Gtk } = imports.gi;
import { Widget } from '../../imports.js';
export const ContextMenuItem = ({ label, onClick }) => Widget({
type: Gtk.MenuItem,
label: `${label}`,
setup: menuItem => {
menuItem.connect("activate", onClick);
}
})

View File

@@ -0,0 +1,70 @@
const { Gdk, Gtk } = imports.gi;
const CLICK_BRIGHTEN_AMOUNT = 0.13;
export function setupCursorHover(button) {
var clicked = false;
var dummy = false;
var cursorX = 0;
var cursorY = 0;
const styleContext = button.get_style_context();
var clickColor = styleContext.get_property('background-color', Gtk.StateFlags.HOVER);
clickColor.green += CLICK_BRIGHTEN_AMOUNT;
clickColor.blue += CLICK_BRIGHTEN_AMOUNT;
clickColor.red += CLICK_BRIGHTEN_AMOUNT;
clickColor = clickColor.to_string();
const display = Gdk.Display.get_default();
button.connect('enter-notify-event', () => {
const cursor = Gdk.Cursor.new_from_name(display, 'pointer');
button.get_window().set_cursor(cursor);
});
button.connect('leave-notify-event', () => {
const cursor = Gdk.Cursor.new_from_name(display, 'default');
button.get_window().set_cursor(cursor);
});
// button.add_events(Gdk.EventMask.POINTER_MOTION_MASK);
// button.connect('motion-notify-event', (widget, event) => {
// [dummy, cursorX, cursorY] = event.get_coords(); // Get the mouse coordinates relative to the widget
// if(!clicked) widget.style = `
// background-image: radial-gradient(circle at ${cursorX}px ${cursorY}px, rgba(0,0,0,0), rgba(0,0,0,0) 0%, rgba(0,0,0,0) 0%, ${clickColor} 0%, ${clickColor} 0%, ${clickColor} 0%, ${clickColor} 0%, rgba(0,0,0,0) 0%, rgba(0,0,0,0) 0%);
// `;
// });
// button.connect('button-press-event', (widget, event) => {
// clicked = true;
// [dummy, cursorX, cursorY] = event.get_coords(); // Get the mouse coordinates relative to the widget
// cursorX = Math.round(cursorX); cursorY = Math.round(cursorY);
// widget.style = `
// background-image: radial-gradient(circle at ${cursorX}px ${cursorY}px, rgba(0,0,0,0), rgba(0,0,0,0) 0%, rgba(0,0,0,0) 0%, ${clickColor} 0%, ${clickColor} 0%, ${clickColor} 0%, ${clickColor} 0%, rgba(0,0,0,0) 0%, rgba(0,0,0,0) 0%);
// `;
// widget.toggleClassName('growingRadial', true);
// widget.style = `
// background-image: radial-gradient(circle at ${cursorX}px ${cursorY}px, rgba(0,0,0,0), rgba(0,0,0,0) 0%, rgba(0,0,0,0) 0%, ${clickColor} 0%, ${clickColor} 0%, ${clickColor} 70%, ${clickColor} 70%, rgba(0,0,0,0) 70%, rgba(0,0,0,0) 70%);
// `
// });
// button.connect('button-release-event', (widget, event) => {
// widget.toggleClassName('growingRadial', false);
// widget.toggleClassName('fadingRadial', false);
// widget.style = `
// background-image: radial-gradient(circle at ${cursorX}px ${cursorY}px, rgba(0,0,0,0), rgba(0,0,0,0) 0%, rgba(0,0,0,0) 0%, rgba(0,0,0,0) 0%, rgba(0,0,0,0) 0%, rgba(0,0,0,0) 70%, rgba(0,0,0,0) 70%, rgba(0,0,0,0) 70%, rgba(0,0,0,0) 70%);
// `
// clicked = false;
// });
}
export function setupCursorHoverAim(button) {
button.connect('enter-notify-event', () => {
const display = Gdk.Display.get_default();
const cursor = Gdk.Cursor.new_from_name(display, 'crosshair');
button.get_window().set_cursor(cursor);
});
button.connect('leave-notify-event', () => {
const display = Gdk.Display.get_default();
const cursor = Gdk.Cursor.new_from_name(display, 'default');
button.get_window().set_cursor(cursor);
});
}

View File

@@ -0,0 +1,7 @@
import { Widget } from '../../imports.js';
export const MaterialIcon = (icon, size, props = {}) => Widget.Label({
className: `icon-material txt-${size}`,
label: icon,
...props,
})

View File

@@ -0,0 +1,73 @@
const { Gdk, Gtk } = imports.gi;
const GObject = imports.gi.GObject;
const Lang = imports.lang;
import { Utils, Widget } from '../../imports.js';
// min-height/min-width for height/width
// background-color/color for background/indicator color
// padding for pad of indicator
// font-size for selected index (0-based)
export const NavigationIndicator = (count, vertical, props) => Widget({
...props,
type: Gtk.DrawingArea,
setup: (area) => {
const styleContext = area.get_style_context();
const width = Math.max(styleContext.get_property('min-width', Gtk.StateFlags.NORMAL), area.get_allocated_width());
const height = Math.max(styleContext.get_property('min-height', Gtk.StateFlags.NORMAL), area.get_allocated_height());
area.set_size_request(width, height);
area.connect('draw', Lang.bind(area, (area, cr) => {
const styleContext = area.get_style_context();
const width = Math.max(styleContext.get_property('min-width', Gtk.StateFlags.NORMAL), area.get_allocated_width());
const height = Math.max(styleContext.get_property('min-height', Gtk.StateFlags.NORMAL), area.get_allocated_height());
// console.log('allocated width/height:', area.get_allocated_width(), '/', area.get_allocated_height())
area.set_size_request(width, height);
const paddingLeft = styleContext.get_padding(Gtk.StateFlags.NORMAL).left;
const paddingRight = styleContext.get_padding(Gtk.StateFlags.NORMAL).right;
const paddingTop = styleContext.get_padding(Gtk.StateFlags.NORMAL).top;
const paddingBottom = styleContext.get_padding(Gtk.StateFlags.NORMAL).bottom;
const selectedCell = styleContext.get_property('font-size', Gtk.StateFlags.NORMAL);
let cellWidth = width;
let cellHeight = height;
if (vertical) cellHeight /= count;
else cellWidth /= count;
const indicatorWidth = cellWidth - paddingLeft - paddingRight;
const indicatorHeight = cellHeight - paddingTop - paddingBottom;
const background_color = styleContext.get_property('background-color', Gtk.StateFlags.NORMAL);
const color = styleContext.get_property('color', Gtk.StateFlags.NORMAL);
cr.setLineWidth(2);
// Background
cr.setSourceRGBA(background_color.red, background_color.green, background_color.blue, background_color.alpha);
cr.rectangle(0, 0, width, height);
cr.fill();
// The indicator line
cr.setSourceRGBA(color.red, color.green, color.blue, color.alpha);
if (vertical) {
cr.rectangle(paddingLeft, paddingTop + cellHeight * selectedCell + indicatorWidth / 2, indicatorWidth, indicatorHeight - indicatorWidth);
cr.stroke();
cr.rectangle(paddingLeft, paddingTop + cellHeight * selectedCell + indicatorWidth / 2, indicatorWidth, indicatorHeight - indicatorWidth);
cr.fill();
cr.arc(paddingLeft + indicatorWidth / 2, paddingTop + cellHeight * selectedCell + indicatorWidth / 2, indicatorWidth / 2, Math.PI, 2 * Math.PI);
cr.fill();
cr.arc(paddingLeft + indicatorWidth / 2, paddingTop + cellHeight * selectedCell + indicatorHeight - indicatorWidth / 2, indicatorWidth / 2, 0, Math.PI);
cr.fill();
}
else {
cr.rectangle(paddingLeft + cellWidth * selectedCell + indicatorHeight / 2, paddingTop, indicatorWidth - indicatorHeight, indicatorHeight);
cr.stroke();
cr.rectangle(paddingLeft + cellWidth * selectedCell + indicatorHeight / 2, paddingTop, indicatorWidth - indicatorHeight, indicatorHeight);
cr.fill();
cr.arc(paddingLeft + cellWidth * selectedCell + indicatorHeight / 2, paddingTop + indicatorHeight / 2, indicatorHeight / 2, 0.5 * Math.PI, 1.5 * Math.PI);
cr.fill();
cr.arc(paddingLeft + cellWidth * selectedCell + indicatorWidth - indicatorHeight / 2, paddingTop + indicatorHeight / 2, indicatorHeight / 2, -0.5 * Math.PI, 0.5 * Math.PI);
cr.fill();
}
}))
},
})

View File

@@ -0,0 +1,298 @@
// This file is for the actual widget for each single notification
const { GLib, Gdk, Gtk } = imports.gi;
import { Utils, Widget } from '../../imports.js';
const { lookUpIcon, timeout } = Utils;
const { Box, EventBox, Icon, Scrollable, Label, Button, Revealer } = Widget;
import { MaterialIcon } from "./materialicon.js";
import { setupCursorHover } from "./cursorhover.js";
const NotificationIcon = (notifObject) => {
// { appEntry, appIcon, image }, urgency = 'normal'
if (notifObject.image) {
return Box({
valign: 'center',
hexpand: false,
className: 'notif-icon',
style: `
background-image: url("${notifObject.image}");
background-size: auto 100%;
background-repeat: no-repeat;
background-position: center;
`,
});
}
let icon = 'NO_ICON';
if (lookUpIcon(notifObject.appIcon))
icon = notifObject.appIcon;
if (lookUpIcon(notifObject.appEntry))
icon = notifObject.appEntry;
return Box({
valign: 'center',
hexpand: false,
className: 'notif-icon',
setup: box => {
if (icon != 'NO_ICON') box.pack_start(Icon({
icon, size: 30,
halign: 'center', hexpand: true,
valign: 'center',
setup: () => {
box.toggleClassName(`notif-icon-material-${notifObject.urgency}`, true);
},
}), false, true, 0);
else box.pack_start(MaterialIcon(`${notifObject.urgency == 'critical' ? 'release_alert' : 'chat'}`, 'hugeass', {
hexpand: true,
setup: () => box.toggleClassName(`notif-icon-material-${notifObject.urgency}`, true),
}), false, true, 0)
}
});
};
export default ({
notifObject,
isPopup = false,
props = {},
} = {}) => {
const command = (isPopup ?
() => notifObject.dismiss() :
() => notifObject.close()
)
const destroyWithAnims = () => {
widget.sensitive = false;
notificationBox.setStyle(rightAnim1);
Utils.timeout(200, () => {
wholeThing.revealChild = false;
});
Utils.timeout(400, () => {
command();
wholeThing.destroy();
});
}
const widget = EventBox({
onHover: (self) => {
self.window.set_cursor(Gdk.Cursor.new_from_name(display, 'grab'));
if (!wholeThing._hovered)
wholeThing._hovered = true;
},
onHoverLost: (self) => {
self.window.set_cursor(null);
if (wholeThing._hovered)
wholeThing._hovered = false;
if(isPopup) {
command();
}
},
onMiddleClick: (self) => {
destroyWithAnims();
}
});
const wholeThing = Revealer({
properties: [
['id', notifObject.id],
['close', undefined],
['hovered', false],
['dragging', false],
['destroyWithAnims', () => destroyWithAnims]
],
revealChild: false,
transition: 'slide_down',
transitionDuration: 200,
child: Box({ // Box to make sure css-based spacing works
homogeneous: true,
})
});
const display = Gdk.Display.get_default();
const notificationContent = Box({
...props,
className: `${isPopup ? 'popup-' : ''}notif-${notifObject.urgency} spacing-h-10`,
children: [
NotificationIcon(notifObject),
Box({
valign: 'center',
vertical: true,
hexpand: true,
children: [
Box({
children: [
Label({
xalign: 0,
className: 'txt-small txt-semibold titlefont',
justify: Gtk.Justification.LEFT,
hexpand: true,
maxWidthChars: 24,
ellipsize: 3,
wrap: true,
useMarkup: notifObject.summary.startsWith('<'),
label: notifObject.summary,
}),
]
}),
Label({
xalign: 0,
className: 'txt-smallie notif-body-${urgency}',
useMarkup: true,
xalign: 0,
justify: Gtk.Justification.LEFT,
wrap: true,
label: notifObject.body,
}),
]
}),
Box({
className: 'spacing-h-5',
children: [
Label({
valign: 'center',
className: 'txt-smaller txt-semibold',
justify: Gtk.Justification.RIGHT,
setup: (label) => {
const messageTime = GLib.DateTime.new_from_unix_local(notifObject.time);
if (messageTime.get_day_of_year() == GLib.DateTime.new_now_local().get_day_of_year()) {
label.label = messageTime.format('%H:%M');
}
else if (messageTime.get_day_of_year() == GLib.DateTime.new_now_local().get_day_of_year() - 1) {
label.label = messageTime.format('%H:%M\nYesterday');
}
else {
label.label = messageTime.format('%H:%M\n%d/%m');
}
}
}),
Button({
className: 'notif-close-btn',
onClicked: () => {
destroyWithAnims()
},
child: MaterialIcon('close', 'large', {
valign: 'center',
}),
setup: (button) => setupCursorHover(button),
}),
]
}),
// what is this? i think it should be at the bottom not on the right
// Box({
// className: 'actions',
// children: actions.map(action => Button({
// className: 'action-button',
// onClicked: () => Notifications.invoke(id, action.id),
// hexpand: true,
// child: Label(action.label),
// })),
// }),
]
})
// Gesture stuff
const gesture = Gtk.GestureDrag.new(widget);
var initialDir = 0;
// in px
const startMargin = 0;
const dragThreshold = 100;
// in rem
const maxOffset = 10.227;
const endMargin = 20.455;
const disappearHeight = 6.818;
const leftAnim1 = `transition: 200ms cubic-bezier(0.05, 0.7, 0.1, 1);
margin-left: -${Number(maxOffset + endMargin)}rem;
margin-right: ${Number(maxOffset + endMargin)}rem;
opacity: 0;`;
const rightAnim1 = `transition: 200ms cubic-bezier(0.05, 0.7, 0.1, 1);
margin-left: ${Number(maxOffset + endMargin)}rem;
margin-right: -${Number(maxOffset + endMargin)}rem;
opacity: 0;`;
const notificationBox = Box({
properties: [
['leftAnim1', leftAnim1],
['rightAnim1', rightAnim1],
['ready', false],
],
homogeneous: true,
children: [notificationContent],
connections: [
[gesture, self => {
var offset = gesture.get_offset()[1];
if (initialDir == 0 && offset != 0)
initialDir = (offset > 0 ? 1 : -1)
if (offset > 0) {
if (initialDir < 0)
self.setStyle(`margin-left: 0px; margin-right: 0px;`);
else
self.setStyle(`
margin-left: ${Number(offset + startMargin)}px;
margin-right: -${Number(offset + startMargin)}px;
`);
}
else if (offset < 0) {
if (initialDir > 0)
self.setStyle(`margin-left: 0px; margin-right: 0px;`);
else {
offset = Math.abs(offset);
self.setStyle(`
margin-right: ${Number(offset + startMargin)}px;
margin-left: -${Number(offset + startMargin)}px;
`);
}
}
wholeThing._dragging = Math.abs(offset) > 10;
if (widget.window)
widget.window.set_cursor(Gdk.Cursor.new_from_name(display, 'grabbing'));
}, 'drag-update'],
[gesture, self => {
if (!self._ready) {
wholeThing.revealChild = true;
self._ready = true;
return;
}
const offset = gesture.get_offset()[1];
if (Math.abs(offset) > dragThreshold && offset * initialDir > 0) {
if (offset > 0) {
self.setStyle(rightAnim1);
widget.sensitive = false;
}
else {
self.setStyle(leftAnim1);
widget.sensitive = false;
}
Utils.timeout(200, () => {
wholeThing.revealChild = false
});
Utils.timeout(400, () => {
command();
wholeThing.destroy();
});
}
else {
self.setStyle(`transition: margin 200ms cubic-bezier(0.05, 0.7, 0.1, 1), opacity 200ms cubic-bezier(0.05, 0.7, 0.1, 1);
margin-left: ${startMargin}px;
margin-right: ${startMargin}px;
margin-bottom: unset; margin-top: unset;
opacity: 1;`);
if (widget.window)
widget.window.set_cursor(Gdk.Cursor.new_from_name(display, 'grab'));
wholeThing._dragging = false;
}
initialDir = 0;
}, 'drag-end'],
],
})
widget.add(notificationBox);
wholeThing.child.children = [widget];
return wholeThing;
}

View File

@@ -0,0 +1,51 @@
import { Widget } from '../../imports.js';
const { Gtk } = imports.gi;
const Lang = imports.lang;
export const RoundedCorner = (place, props) => Widget({
...props,
type: Gtk.DrawingArea,
halign: place.includes('left') ? 'start' : 'end',
valign: place.includes('top') ? 'start' : 'end',
setup: widget => {
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);
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 'topleft':
cr.arc(r, r, r, Math.PI, 3 * Math.PI / 2);
cr.lineTo(0, 0);
break;
case 'topright':
cr.arc(0, r, r, 3 * Math.PI / 2, 2 * Math.PI);
cr.lineTo(r, 0);
break;
case 'bottomleft':
cr.arc(r, 0, r, Math.PI / 2, Math.PI);
cr.lineTo(0, r);
break;
case 'bottomright':
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();
}));
},
});

View File

@@ -0,0 +1,69 @@
const { Gdk, Gtk } = imports.gi;
import { App, Service, Utils, Widget } from '../../imports.js';
const { execAsync, exec } = Utils;
import { setupCursorHover, setupCursorHoverAim } from "./cursorhover.js";
import { MaterialIcon } from './materialicon.js';
export const searchItem = ({ materialIconName, name, actionName, content, onActivate }) => {
const actionText = Widget.Revealer({
revealChild: false,
transition: "crossfade",
transitionDuration: 200,
child: Widget.Label({
className: 'overview-search-results-txt txt txt-small txt-action',
label: `${actionName}`,
})
});
const actionTextRevealer = Widget.Revealer({
revealChild: false,
transition: "slide_left",
transitionDuration: 300,
child: actionText,
})
return Widget.Button({
className: 'overview-search-result-btn',
onClicked: onActivate,
child: Widget.Box({
children: [
Widget.Box({
vertical: false,
children: [
Widget.Label({
className: `icon-material overview-search-results-icon`,
label: `${materialIconName}`,
}),
Widget.Box({
vertical: true,
children: [
Widget.Label({
halign: 'start',
className: 'overview-search-results-txt txt txt-smallie txt-subtext',
label: `${name}`,
truncate: "end",
}),
Widget.Label({
halign: 'start',
className: 'overview-search-results-txt txt txt-norm',
label: `${content}`,
truncate: "end",
}),
]
}),
Widget.Box({ hexpand: true }),
actionTextRevealer,
],
})
]
}),
connections: [
['focus-in-event', (button) => {
actionText.revealChild = true;
actionTextRevealer.revealChild = true;
}],
['focus-out-event', (button) => {
actionText.revealChild = false;
actionTextRevealer.revealChild = false;
}],
]
});
}

View File

@@ -0,0 +1,9 @@
const { Gdk, Gtk } = imports.gi;
import { App, Service, Utils, Widget } from '../../imports.js';
const { execAsync, exec } = Utils;
import { setupCursorHover, setupCursorHoverAim } from "./cursorhover.js";
import { MaterialIcon } from './materialicon.js';
export const separatorLine = Widget.Box({
className: 'separator-line',
})

View File

@@ -0,0 +1,111 @@
import { App, Utils, Widget } from '../imports.js';
const { execAsync, exec } = Utils;
import { MaterialIcon } from "./lib/materialicon.js";
import { setupCursorHover } from "./lib/cursorhover.js";
const RECORD_SCRIPT_DIR = `${App.configDir}/scripts/record-script.sh`;
const RECORDER_PROCESS = 'record-script.sh';
const CLOSE_ANIM_TIME = 150;
async function toggleSystemdService(serviceName, button) {
const serviceState = exec(`systemctl is-enabled ${serviceName}`) == 'enabled';
// console.log(`pkexec bash -c "systemctl ${serviceState ? 'disable' : 'enable'} ${serviceName}"`)
exec(`pkexec bash -c "systemctl ${serviceState ? 'disable' : 'enable'} ${serviceName}"`);
const newServiceState = exec(`systemctl is-enabled ${serviceName}`) == 'enabled';
button.toggleClassName('sidebar-button-active', newServiceState);
serviceState.toggleClassName('invisible', newServiceState);
}
const ModuleRecord = (props = {}) => Widget.Button({
...props,
className: 'button-minsize sidebar-button-nopad sidebar-button-alone-normal txt-small',
onClicked: () => {
execAsync(['bash', '-c', RECORD_SCRIPT_DIR]).catch(print);
setTimeout(() => {
button.toggleClassName('sidebar-button-active', exec(`pidof ${RECORDER_PROCESS} >/dev/null && echo 1 || echo`) == '1');
}, CLOSE_ANIM_TIME);
},
child: MaterialIcon('screen_record', 'larger'),
setup: button => {
button.toggleClassName('sidebar-button-active', exec(`pidof ${RECORDER_PROCESS} >/dev/null && echo 1 || echo`));
setupCursorHover(button);
}
})
const SystemdService = (serviceName) => {
const serviceState = Widget.Label({
className: `icon-material txt-larger`,
label: 'check',
setup: label => {
// label.toggleClassName('invisible', exec(`bash -c "systemctl is-enabled ${serviceName} >/dev/null && echo ON || echo OFF"`) == 'OFF');
}
});
return Widget.Button({
className: 'button-minsize sidebar-button sidebar-button-alone-normal txt-small',
onClicked: (button) => {
toggleSystemdService(serviceName, button);
},
setup: button => {
button.toggleClassName('sidebar-button-active', exec(`systemctl is-enabled ${serviceName}`) == 'enabled');
setupCursorHover(button);
},
child: Widget.Box({
setup: box => {
box.pack_start(Widget.Label({
xalign: 0,
label: serviceName,
}), true, true, 0);
// box.pack_end(serviceState, false, false, 0);
}
})
});
}
export const ModuleMiscToggles = () => {
const PowerSavers = Widget.Revealer({
revealChild: false,
transition: 'slide_left',
transitionDuration: 100,
child: Widget.Box({
className: 'spacing-v-5 margin-right-10',
vertical: true,
children: [
SystemdService('tlp'),
SystemdService('auto-cpufreq'),
]
})
})
const ModulePowerSavers = Widget.Button({
className: 'button-minsize sidebar-button-nopad sidebar-button-alone-normal txt-small',
child: MaterialIcon('keyboard_arrow_leftenergy_savings_leaf', 'larger', {
xalign: 0.2,
}),
onClicked: (button) => {
const revealed = PowerSavers.revealChild;
PowerSavers.revealChild = !revealed;
button.toggleClassName('sidebar-button-active', !revealed);
button.child.label = revealed ? 'keyboard_arrow_leftenergy_savings_leaf' : 'keyboard_arrow_rightenergy_savings_leaf';
},
setup: (button) => setupCursorHover(button),
})
return Widget.Box({
className: 'sidebar-group spacing-h-10',
children: [
PowerSavers,
Widget.Box({
vertical: true,
className: 'spacing-v-5',
children: [
ModulePowerSavers,
Widget.Box({
className: 'spacing-h-5',
children: [
ModuleNightLight(),
ModuleRecord(),
]
})
]
})
]
});
}

View File

@@ -0,0 +1,78 @@
import { Service, Utils, Widget } from '../imports.js';
import Mpris from 'resource:///com/github/Aylur/ags/service/mpris.js';
import Audio from 'resource:///com/github/Aylur/ags/service/audio.js';
const { execAsync, exec } = Utils;
import { AnimatedCircProg } from "./lib/animatedcircularprogress.js";
const TrackProgress = () => {
const _updateProgress = (circprog) => {
const mpris = Mpris.getPlayer('');
if (!mpris) return;
// Set circular progress (font size cuz that's how this hacky circprog works)
circprog.style = `font-size: ${mpris.position / mpris.length * 100}px;`
}
return AnimatedCircProg({
className: 'bar-music-circprog',
valign: 'center',
connections: [ // Update on change/once every 3 seconds
[Mpris, _updateProgress],
[3000, _updateProgress]
]
})
}
export const ModuleMusic = () => Widget.EventBox({
onScrollUp: () => execAsync('hyprctl dispatch workspace -1'),
onScrollDown: () => execAsync('hyprctl dispatch workspace +1'),
onSecondaryClick: () => Mpris.getPlayer('')?.next(),
onMiddleClick: () => Mpris.getPlayer('')?.playPause(),
child: Widget.Box({
className: 'bar-group-margin bar-sides',
children: [
Widget.Box({
className: 'bar-group bar-group-standalone bar-group-pad-music spacing-h-10',
children: [
Widget.Box({ // Wrap a box cuz overlay can't have margins itself
homogeneous: true,
children: [Widget.Overlay({
child: Widget.Box({
valign: 'center',
className: 'bar-music-playstate',
children: [Widget.Label({
valign: 'center',
className: 'bar-music-playstate-txt',
connections: [[Mpris, label => {
const mpris = Mpris.getPlayer('');
label.label = `${mpris !== null && mpris.playBackStatus == 'Playing' ? '' : ''}`;
}]],
})],
connections: [[Mpris, label => {
const mpris = Mpris.getPlayer('');
if (!mpris) return;
label.toggleClassName('bar-music-playstate-playing', mpris !== null && mpris.playBackStatus == 'Playing');
label.toggleClassName('bar-music-playstate', mpris !== null || mpris.playBackStatus == 'Paused');
}]],
}),
overlays: [
TrackProgress(),
]
})]
}),
Widget.Scrollable({
hexpand: true,
child: Widget.Label({
className: 'txt txt-smallie',
connections: [[Mpris, label => {
const mpris = Mpris.getPlayer('');
if (mpris)
label.label = `${mpris.trackTitle}${mpris.trackArtists.join(', ')}`;
else
label.label = 'No mewwsic';
}]],
})
})
]
})
]
})
});

View File

@@ -0,0 +1,9 @@
import { Service, Utils, Widget } from '../imports.js';
const { Box, CenterBox, Label } = Widget;
const { Mpris } = Service;
const { timeout } = Utils;
import { BluetoothIndicator, NetworkIndicator } from "./statusicons.js";
export const ModuleMusicControls = () => Box({
})

View File

@@ -0,0 +1,123 @@
// This file is for the notification widget on the sidebar
// For the popup notifications, see onscreendisplay.js
// The actual widget for each single notification is in lib/notification.js
const { GLib, Gtk } = imports.gi;
import { Service, Utils, Widget } from '../imports.js';
import Notifications from 'resource:///com/github/Aylur/ags/service/notifications.js';
const { lookUpIcon, timeout } = Utils;
const { Box, Icon, Scrollable, Label, Button, Revealer } = Widget;
import { MaterialIcon } from "./lib/materialicon.js";
import { setupCursorHover } from "./lib/cursorhover.js";
import Notification from "./lib/notification.js";
const NotificationList = Box({
vertical: true,
valign: 'start',
className: 'spacing-v-5-revealer',
connections: [
[Notifications, (box, id) => {
if (box.children.length == 0) {
Notifications.notifications
.forEach(n => {
box.pack_end(Notification({
notifObject: n,
isPopup: false,
}), false, false, 0)
});
box.show_all();
}
else if (id) {
const notif = Notifications.getNotification(id);
const NewNotif = Notification({
notifObject: notif,
isPopup: false,
});
if (NewNotif) {
box.pack_end(NewNotif, false, false, 0);
box.show_all();
}
}
}, 'notified'],
[Notifications, (box, id) => {
if (!id) return;
for (const ch of box.children) {
if (ch._id === id) {
ch._destroyWithAnims();
}
}
}, 'closed'],
[Notifications, box => box.visible = Notifications.notifications.length > 0],
],
});
export default (props) => {
const listTitle = Revealer({
revealChild: false,
connections: [[Notifications, (revealer) => {
revealer.revealChild = (Notifications.notifications.length > 0);
}]],
child: Box({
valign: 'start',
className: 'sidebar-group-invisible txt',
children: [
Label({
hexpand: true,
xalign: 0,
className: 'txt-title-small',
label: 'Notifications',
}),
Button({
className: 'notif-closeall-btn',
onClicked: () => {
Notifications.clear();
},
child: Box({
className: 'spacing-h-5',
children: [
MaterialIcon('clear_all', 'norm'),
Label({
className: 'txt-small',
label: 'Clear',
})
]
}),
setup: button => {
setupCursorHover(button);
},
})
]
})
});
const listContents = Scrollable({
hexpand: true,
hscroll: 'never',
vscroll: 'automatic',
child: Widget({
type: Gtk.Viewport,
className: 'sidebar-viewport',
setup: (viewport) => {
viewport.add(Box({
vexpand: true,
children: [NotificationList],
}));
}
})
});
listContents.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
const vScrollbar = listContents.get_vscrollbar();
vScrollbar.get_style_context().add_class('sidebar-scrollbar');
return Box({
...props,
className: 'sidebar-group-invisible spacing-v-5',
vertical: true,
children: [
listTitle,
listContents,
]
});
}

View File

@@ -0,0 +1,192 @@
// This file is for brightness/volume indicator and popup notifications
// For the notification widget on the sidebar, see notificationlist.js
// The actual widget for each single notification is in lib/notification.js
const { GLib, Gtk } = imports.gi;
import { App, Service, Utils, Widget } from '../imports.js';
import Audio from 'resource:///com/github/Aylur/ags/service/audio.js';
import Notifications from 'resource:///com/github/Aylur/ags/service/notifications.js';
const { Box, EventBox, Icon, Scrollable, Label, Button, Revealer } = Widget;
import Brightness from '../scripts/brightness.js';
import Indicator from '../scripts/indicator.js';
import Notification from './lib/notification.js';
const OsdValue = (name, labelConnections, progressConnections, props = {}) => Widget.Box({ // Volume
...props,
vertical: true,
className: 'osd-bg osd-value',
hexpand: true,
children: [
Widget.Box({
vexpand: true,
children: [
Widget.Label({
xalign: 0, yalign: 0, hexpand: true,
className: 'osd-label',
label: `${name}`,
}),
Widget.Label({
hexpand: false, className: 'osd-value-txt',
label: '100',
connections: labelConnections,
}),
]
}),
Widget.ProgressBar({
className: 'osd-progress',
hexpand: true,
vertical: false,
connections: progressConnections,
})
],
});
const brightnessIndicator = OsdValue('Brightness',
[[Brightness, self => {
self.label = `${Math.round(Brightness.screen_value * 100)}`;
}, 'notify::screen-value']],
[[Brightness, (progress) => {
const updateValue = Brightness.screen_value;
progress.value = updateValue;
}, 'notify::screen-value']],
)
const volumeIndicator = OsdValue('Volume',
[[Audio, (label) => {
label.label = `${Math.round(Audio.speaker?.volume * 100)}`;
}]],
[[Audio, (progress) => {
const updateValue = Audio.speaker?.volume;
if (!isNaN(updateValue)) progress.value = updateValue;
}]],
);
const indicatorValues = Widget.Revealer({
transition: 'slide_down',
connections: [
[Indicator, (revealer, value) => {
revealer.revealChild = (value > -1);
}, 'popup'],
],
child: Widget.Box({
halign: 'center',
vertical: false,
children: [
brightnessIndicator,
volumeIndicator,
]
})
});
const PopupNotification = (notifObject) => Widget.Box({
homogeneous: true,
children: [
Widget.EventBox({
onHoverLost: () => {
notifObject.dismiss();
},
child: Widget.Revealer({
revealChild: true,
child: Widget.Box({
children: [Notification({
notifObject: notifObject,
isPopup: true,
props: { halign: 'fill' },
})],
}),
})
})
]
})
const naiveNotifPopupList = Widget.Box({
vertical: true,
className: 'spacing-v-5',
connections: [
[Notifications, (box) => {
box.children = Notifications.popups.reverse()
.map(notifItem => PopupNotification(notifItem));
}],
],
})
const notifPopupList = Box({
vertical: true,
className: 'spacing-v-5-revealer',
properties: [
['map', new Map()],
['dismiss', (box, id, force = false) => {
if (!id || !box._map.has(id) || box._map.get(id)._hovered && !force)
return;
const notif = box._map.get(id);
// console.log(notif);
notif.revealChild = false;
Utils.timeout(200, () => {
notif._destroyWithAnims();
})
}],
['notify', (box, id) => {
if (!id || Notifications.dnd)
return;
if (!Notifications.getNotification(id))
return;
box._map.delete(id);
const notif = Notifications.getNotification(id);
box._map.set(id, Notification({
notifObject: notif,
isPopup: true,
}));
box.children = Array.from(box._map.values()).reverse();
Utils.timeout(10, () => {
box.get_parent().revealChild = true;
});
box._map.get(id).interval = Utils.interval(4500, () => {
const notif = box._map.get(id);
if (!notif._hovered) {
if (notif.interval) {
Utils.timeout(500, () => notif.destroy());
GLib.source_remove(notif.interval);
notif.interval = undefined;
}
}
});
}],
],
connections: [
[Notifications, (box, id) => box._notify(box, id), 'notified'],
[Notifications, (box, id) => box._dismiss(box, id), 'dismissed'],
[Notifications, (box, id) => box._dismiss(box, id, true), 'closed'],
],
});
const notificationPopups = Widget.Revealer({
className: 'osd-notifs',
transition: 'slide_down',
connections: [[Notifications, (self) => {
self.revealChild = Notifications.popups.length > 0;
}]],
child: notifPopupList,
})
export default () => Widget.EventBox({
onHover: () => { //make the widget hide when hovering
Indicator.popup(-1);
},
child: Widget.Box({
vertical: true,
style: 'padding: 1px;',
children: [
indicatorValues,
notificationPopups,
]
})
});

View File

@@ -0,0 +1,114 @@
const { Gdk, Gtk } = imports.gi;
import { App, Service, Utils, Widget } from '../imports.js';
const { Box, EventBox, Button, Revealer } = Widget;
const { execAsync, exec } = Utils;
import { setupCursorHover, setupCursorHoverAim } from "./lib/cursorhover.js";
import { MaterialIcon } from './lib/materialicon.js';
import { separatorLine } from './lib/separator.js';
import { defaultOskLayout, oskLayouts } from '../data/keyboardlayouts.js';
const keyboardLayout = defaultOskLayout;
const keyboardJson = oskLayouts[keyboardLayout];
execAsync(`ydotoold`).catch(print); // Start ydotool daemon
function releaseAllKeys() {
const keycodes = Array.from(Array(249).keys());
execAsync([`ydotool`, `key`, ...keycodes.map(keycode => `${keycode}:0`)])
.then(console.log('Released all keys'))
.catch(print);
}
var modsPressed = false;
const keyboardControlButton = (icon, text, runFunction) => Button({
className: 'osk-control-button spacing-h-10',
onClicked: () => runFunction(),
child: Widget.Box({
children: [
MaterialIcon(icon, 'norm'),
Widget.Label({
label: `${text}`,
}),
]
})
})
const keyboardControls = Box({
vertical: true,
className: 'spacing-v-5',
children: [
Button({
className: 'osk-control-button txt-norm icon-material',
onClicked: () => {
releaseAllKeys();
App.toggleWindow('osk');
},
label: 'keyboard_hide',
}),
Button({
className: 'osk-control-button txt-norm',
label: `${keyboardJson['name_short']}`,
}),
Button({
className: 'osk-control-button txt-norm icon-material',
onClicked: () => { // TODO: Proper clipboard widget, since fuzzel doesn't receive mouse inputs
execAsync([`bash`, `-c`, "pkill fuzzel || cliphist list | fuzzel --no-fuzzy --dmenu | cliphist decode | wl-copy"]).catch(print);
},
label: 'assignment',
}),
]
})
const keyboardItself = (kbJson) => {
return Box({
vertical: true,
className: 'spacing-v-5',
children: kbJson.keys.map(row => Box({
vertical: false,
className: 'spacing-h-5',
children: row.map(key => {
return Button({
className: `osk-key osk-key-${key.shape}`,
hexpand: (key.shape == "space" || key.shape == "expand"),
label: key.label,
setup: (button) => {
let pressed = false;
if (key.keytype == "normal") {
button.connect('pressed', () => { // mouse down
execAsync(`ydotool key ${key.keycode}:1`);
});
button.connect('clicked', () => { // release
execAsync(`ydotool key ${key.keycode}:0`);
});
}
else if (key.keytype == "modkey") {
button.connect('pressed', () => { // release
if (pressed) {
execAsync(`ydotool key ${key.keycode}:0`);
button.toggleClassName('osk-key-active', false);
pressed = false;
}
else {
execAsync(`ydotool key ${key.keycode}:1`);
button.toggleClassName('osk-key-active', true);
pressed = true;
modsPressed = true;
}
});
}
}
})
})
}))
})
}
export default () => Box({
vexpand: true,
hexpand: true,
className: 'osk-window spacing-h-10',
children: [
keyboardControls,
separatorLine,
keyboardItself(keyboardJson),
],
});

View File

@@ -0,0 +1,580 @@
const { Gdk, Gtk } = imports.gi;
import { App, Service, Utils, Widget } from '../imports.js';
import Applications from 'resource:///com/github/Aylur/ags/service/applications.js';
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
const { execAsync, exec } = Utils;
import { setupCursorHover, setupCursorHoverAim } from "./lib/cursorhover.js";
import { MaterialIcon } from './lib/materialicon.js';
import { searchItem } from './lib/searchitem.js';
import { ContextMenuItem } from './lib/contextmenuitem.js';
import Todo from "../scripts/todo.js";
var searching = false;
// Add math funcs
const { abs, sin, cos, tan, cot, asin, acos, atan, acot } = Math;
const pi = Math.PI;
// trigonometric funcs for deg
const sind = x => sin(x * pi / 180);
const cosd = x => cos(x * pi / 180);
const tand = x => tan(x * pi / 180);
const cotd = x => cot(x * pi / 180);
const asind = x => asin(x) * 180 / pi;
const acosd = x => acos(x) * 180 / pi;
const atand = x => atan(x) * 180 / pi;
const acotd = x => acot(x) * 180 / pi;
const MAX_RESULTS = 10;
const OVERVIEW_SCALE = 0.18; // = overview workspace box / screen size
const TARGET = [Gtk.TargetEntry.new('text/plain', Gtk.TargetFlags.SAME_APP, 0)];
const searchPromptTexts = [
'Try "Kolourpaint"',
'Try "6*cos(pi)"',
'Try "sudo pacman -Syu"',
'Try "How to basic"',
'Drag n\' drop to move windows',
'Type to search',
]
function launchCustomCommand(command) {
App.closeWindow('overview');
const args = command.split(' ');
if (args[0] == '>raw') { // Mouse raw input
execAsync([`bash`, `-c`, `hyprctl keyword input:force_no_accel $(( 1 - $(hyprctl getoption input:force_no_accel -j | gojq ".int") ))`, `&`]).catch(print);
}
else if (args[0] == '>img') { // Change wallpaper
execAsync([`bash`, `-c`, `${App.configDir}/scripts/color_generation/switchwall.sh`, `&`]).catch(print);
}
else if (args[0] == '>light') { // Light mode
execAsync([`bash`, `-c`, `mkdir -p ~/.cache/ags/user && echo "-l" > ~/.cache/ags/user/colormode.txt`, `&`]).catch(print);
}
else if (args[0] == '>dark') { // Dark mode
execAsync([`bash`, `-c`, `mkdir -p ~/.cache/ags/user && echo "" > ~/.cache/ags/user/colormode.txt`, `&`]).catch(print);
}
else if (args[0] == '>material') { // Light mode
execAsync([`bash`, `-c`, `mkdir -p ~/.cache/ags/user && echo "material" > ~/.cache/ags/user/colorbackend.txt`, `&`]).catch(print);
}
else if (args[0] == '>pywal') { // Dark mode
execAsync([`bash`, `-c`, `mkdir -p ~/.cache/ags/user && echo "pywal" > ~/.cache/ags/user/colorbackend.txt`, `&`]).catch(print);
}
else if (args[0] == '>todo') { // Todo
Todo.add(args.slice(1).join(' '));
}
else if (args[0] == '>shutdown') { // Shut down
execAsync([`bash`, `-c`, `systemctl poweroff`]).catch(print);
}
else if (args[0] == '>reboot') { // Reboot
execAsync([`bash`, `-c`, `systemctl reboot`]).catch(print);
}
else if (args[0] == '>sleep') { // Sleep
execAsync([`bash`, `-c`, `systemctl suspend`]).catch(print);
}
else if (args[0] == '>logout') { // Log out
execAsync([`bash`, `-c`, `loginctl terminate-user $USER`]).catch(print);
}
}
function execAndClose(command, terminal) {
App.closeWindow('overview');
if (terminal) {
execAsync([`bash`, `-c`, `foot fish -C "${command}"`, `&`]).catch(print);
}
else
execAsync(command).catch(print);
}
function startsWithNumber(str) {
var pattern = /^\d/;
return pattern.test(str);
}
function substitute(str) {
const subs = [
{ from: 'code-url-handler', to: 'visual-studio-code' },
{ from: 'Code', to: 'visual-studio-code' },
{ from: 'GitHub Desktop', to: 'github-desktop' },
{ from: 'wpsoffice', to: 'wps-office2019-kprometheus' },
{ from: 'gnome-tweaks', to: 'org.gnome.tweaks' },
{ from: 'Minecraft* 1.20.1', to: 'minecraft' },
{ from: '', to: 'image-missing' },
];
for (const { from, to } of subs) {
if (from === str)
return to;
}
return str;
}
function destroyContextMenu(menu) {
if (menu !== null) {
menu.remove_all();
menu.destroy();
menu = null;
}
}
const CalculationResultButton = ({ result, text }) => searchItem({
materialIconName: 'calculate',
name: `Math result`,
actionName: "Copy",
content: `${result}`,
onActivate: () => {
App.closeWindow('overview');
console.log(result);
execAsync(['bash', '-c', `wl-copy '${result}'`, `&`]).catch(print);
},
});
const DesktopEntryButton = (app) => {
const actionText = Widget.Revealer({
revealChild: false,
transition: "crossfade",
transitionDuration: 200,
child: Widget.Label({
className: 'overview-search-results-txt txt txt-small txt-action',
label: 'Launch',
})
});
const actionTextRevealer = Widget.Revealer({
revealChild: false,
transition: "slide_left",
transitionDuration: 300,
child: actionText,
});
return Widget.Button({
className: 'overview-search-result-btn',
onClicked: () => {
App.closeWindow('overview');
app.launch();
},
child: Widget.Box({
children: [
Widget.Box({
vertical: false,
children: [
Widget.Icon({
className: 'overview-search-results-icon',
icon: app.iconName,
size: 35, // TODO: Make this follow font size. made for 11pt.
}),
Widget.Label({
className: 'overview-search-results-txt txt txt-norm',
label: app.name,
}),
Widget.Box({ hexpand: true }),
actionTextRevealer,
]
})
]
}),
connections: [
['focus-in-event', (button) => {
actionText.revealChild = true;
actionTextRevealer.revealChild = true;
}],
['focus-out-event', (button) => {
actionText.revealChild = false;
actionTextRevealer.revealChild = false;
}],
]
})
}
const ExecuteCommandButton = ({ command, terminal = false }) => searchItem({
materialIconName: `${terminal ? 'terminal' : 'settings_b_roll'}`,
name: `Run command`,
actionName: `Execute ${terminal ? 'in terminal' : ''}`,
content: `${command}`,
onActivate: () => execAndClose(command, terminal),
})
const CustomCommandButton = ({ text = '' }) => searchItem({
materialIconName: 'settings_suggest',
name: 'Action',
actionName: 'Run',
content: `${text}`,
onActivate: () => {
App.closeWindow('overview');
launchCustomCommand(text);
},
});
const SearchButton = ({ text = '' }) => searchItem({
materialIconName: 'travel_explore',
name: 'Search Google',
actionName: 'Go',
content: `${text}`,
onActivate: () => {
App.closeWindow('overview');
execAsync(['xdg-open', `https://www.google.com/search?q=${text}`]).catch(print);
},
});
const ContextWorkspaceArray = ({ label, onClickBinary, thisWorkspace }) => Widget({
type: Gtk.MenuItem,
label: `${label}`,
setup: menuItem => {
let submenu = new Gtk.Menu();
submenu.className = 'menu';
for (let i = 1; i <= 10; i++) {
let button = new Gtk.MenuItem({ label: `${i}` });
button.connect("activate", () => {
execAsync([`${onClickBinary}`, `${thisWorkspace}`, `${i}`]).catch(print);
});
submenu.append(button);
}
menuItem.set_reserve_indicator(true);
menuItem.set_submenu(submenu);
}
})
const client = ({ address, size: [w, h], workspace: { id, name }, class: c, title }) => Widget.Button({
className: 'overview-tasks-window',
halign: 'center',
valign: 'center',
onClicked: () => {
execAsync([`bash`, `-c`, `hyprctl dispatch focuswindow address:${address}`, `&`]).catch(print);
App.closeWindow('overview');
},
onMiddleClick: () => execAsync([`bash`, `-c`, `hyprctl dispatch closewindow address:${address}`, `&`]).catch(print),
onSecondaryClick: (button) => {
button.toggleClassName('overview-tasks-window-selected', true);
const menu = Widget({
type: Gtk.Menu,
className: 'menu',
setup: menu => {
menu.append(ContextMenuItem({ label: "Close (Middle-click)", onClick: () => { execAsync([`bash`, `-c`, `hyprctl dispatch closewindow address:${address}`, `&`]).catch(print); destroyContextMenu(menu); } }));
menu.append(ContextWorkspaceArray({ label: "Dump windows to workspace", onClickBinary: `${App.configDir}/scripts/dumptows`, thisWorkspace: Number(id) }));
menu.append(ContextWorkspaceArray({ label: "Swap windows with workspace", onClickBinary: `${App.configDir}/scripts/dumptows`, thisWorkspace: Number(id) }));
menu.show_all();
}
});
menu.connect("deactivate", () => {
button.toggleClassName('overview-tasks-window-selected', false);
})
menu.connect("selection-done", () => {
button.toggleClassName('overview-tasks-window-selected', false);
})
menu.popup_at_pointer(null); // Show the menu at the pointer's position
},
child: Widget.Box({
vertical: true,
children: [
Widget.Icon({
style: `
min-width: ${w * OVERVIEW_SCALE - 4}px;
min-height: ${h * OVERVIEW_SCALE - 4}px;
`,
size: Math.min(w, h) * OVERVIEW_SCALE / 2.5,
icon: substitute(c),
}),
Widget.Scrollable({
hexpand: true,
vexpand: true,
child: Widget.Label({
style: `
font-size: ${Math.min(w, h) * OVERVIEW_SCALE / 20}px;
`,
label: title,
})
})
]
}),
tooltipText: `${c}: ${title}`,
setup: (button) => {
setupCursorHoverAim(button);
button.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, TARGET, Gdk.DragAction.MOVE);
button.drag_source_set_icon_name(substitute(c));
// button.drag_source_set_icon_gicon(icon);
button.connect('drag-begin', (button) => { // On drag start, add the dragging class
button.toggleClassName('overview-tasks-window-dragging', true);
});
button.connect('drag-data-get', (_w, _c, data) => { // On drag finish, give address
data.set_text(address, address.length);
button.toggleClassName('overview-tasks-window-dragging', false);
});
// button.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, TARGET, Gdk.DragAction.COPY);
// button.connect('drag-data-get', (_w, _c, data) => data.set_text(address, address.length));
// button.connect('drag-begin', (_, context) => {
// Gtk.drag_set_icon_surface(context, createSurfaceFromWidget(button));
// button.toggleClassName('hidden', true);
// });
// button.connect('drag-end', () => button.toggleClassName('hidden', false));
},
});
const workspace = index => {
const fixed = Gtk.Fixed.new();
const widget = Widget.Box({
className: 'overview-tasks-workspace',
valign: 'center',
style: `
min-width: ${SCREEN_WIDTH * OVERVIEW_SCALE}px;
min-height: ${SCREEN_HEIGHT * OVERVIEW_SCALE}px;
`,
connections: [[Hyprland, box => {
box.toggleClassName('active', Hyprland.active.workspace.id === index);
}]],
children: [Widget.EventBox({
hexpand: true,
vexpand: true,
onPrimaryClickRelease: () => {
execAsync([`bash`, `-c`, `hyprctl dispatch workspace ${index}`, `&`]).catch(print);
App.closeWindow('overview');
},
// onSecondaryClick: (eventbox) => {
// const menu = Widget({
// type: Gtk.Menu,
// setup: menu => {
// menu.append(ContextWorkspaceArray({ label: "Dump windows to workspace", onClickBinary: `${App.configDir}/scripts/dumptows`, thisWorkspace: Number(index) }));
// menu.append(ContextWorkspaceArray({ label: "Swap windows with workspace", onClickBinary: `${App.configDir}/scripts/dumptows`, thisWorkspace: Number(index) }));
// menu.show_all();
// }
// });
// menu.popup_at_pointer(null); // Show the menu at the pointer's position
// },
setup: eventbox => {
eventbox.drag_dest_set(Gtk.DestDefaults.ALL, TARGET, Gdk.DragAction.COPY);
eventbox.connect('drag-data-received', (_w, _c, _x, _y, data) => {
execAsync([`bash`, `-c`, `hyprctl dispatch movetoworkspacesilent ${index},address:${data.get_text()}`, `&`]).catch(print);
});
},
child: fixed,
})],
});
widget.update = clients => {
clients = clients.filter(({ workspace: { id } }) => id === index);
// this is for my monitor layout
// shifts clients back by SCREEN_WIDTHpx if necessary
clients = clients.map(client => {
// console.log(client);
const [x, y] = client.at;
if (x > SCREEN_WIDTH)
client.at = [x - SCREEN_WIDTH, y];
return client;
});
fixed.get_children().forEach(ch => ch.destroy());
clients.forEach(c => c.mapped && fixed.put(client(c), c.at[0] * OVERVIEW_SCALE, c.at[1] * OVERVIEW_SCALE));
fixed.show_all();
};
return widget;
};
const arr = (s, n) => {
const array = [];
for (let i = 0; i < n; i++)
array.push(s + i);
return array;
};
const OverviewRow = ({ startWorkspace = 1, workspaces = 5, windowName = 'overview' }) => Widget.Box({
children: arr(startWorkspace, workspaces).map(workspace),
properties: [['update', box => {
execAsync('hyprctl -j clients').then(clients => {
const json = JSON.parse(clients);
box.get_children().forEach(ch => ch.update(json));
}).catch(print);
}]],
setup: box => box._update(box),
connections: [[Hyprland, box => {
if (!App.getWindow(windowName).visible)
return;
box._update(box);
}]],
});
export const SearchAndWindows = () => {
var _appSearchResults = [];
const clickOutsideToClose = Widget.EventBox({
onPrimaryClick: () => App.closeWindow('overview'),
onSecondaryClick: () => App.closeWindow('overview'),
onMiddleClick: () => App.closeWindow('overview'),
});
const resultsBox = Widget.Box({
className: 'spacing-v-15 overview-search-results',
vertical: true,
vexpand: true,
});
const resultsRevealer = Widget.Revealer({
transitionDuration: 200,
revealChild: false,
transition: 'slide_down',
// duration: 200,
halign: 'center',
child: resultsBox,
});
const overviewRevealer = Widget.Revealer({
revealChild: true,
transition: 'slide_down',
transitionDuration: 200,
child: Widget.Box({
vertical: true,
className: 'overview-tasks',
children: [
OverviewRow({ startWorkspace: 1, workspaces: 5 }),
OverviewRow({ startWorkspace: 6, workspaces: 5 }),
]
}),
});
const entryPromptRevealer = Widget.Revealer({
transition: 'crossfade',
transitionDuration: 150,
revealChild: true,
halign: 'center',
child: Widget.Label({
className: 'overview-search-prompt txt-small txt',
label: searchPromptTexts[Math.floor(Math.random() * searchPromptTexts.length)],
})
});
const entryIconRevealer = Widget.Revealer({
transition: 'crossfade',
transitionDuration: 150,
revealChild: false,
halign: 'end',
child: Widget.Label({
className: 'txt txt-large icon-material overview-search-icon',
label: 'search',
}),
});
const entryIcon = Widget.Box({
className: 'overview-search-prompt-box',
setup: box => box.pack_start(entryIconRevealer, true, true, 0),
});
const entry = Widget.Entry({
className: 'overview-search-box txt-small txt',
halign: 'center',
onAccept: ({ text }) => { // This is when you press Enter
const isAction = text.startsWith('>');
if (startsWithNumber(text)) { // Eval on typing is dangerous, this is a workaround
try {
const fullResult = eval(text);
// copy
execAsync(['bash', '-c', `wl-copy '${fullResult}'`, `&`]).catch(print);
App.closeWindow('overview');
return;
} catch (e) {
// console.log(e);
}
}
if (_appSearchResults.length > 0) {
App.closeWindow('overview');
_appSearchResults[0].launch();
return;
}
else if (text[0] == '>') { // Custom commands
launchCustomCommand(text);
return;
}
// Fallback: Execute command
if (!isAction && exec(`bash -c "command -v ${text.split(' ')[0]}"`) != '') {
if (text.startsWith('sudo'))
execAndClose(text, true);
else
execAndClose(text, false);
}
else {
App.closeWindow('overview');
execAsync(['xdg-open', `https://www.google.com/search?q=${text}`]).catch(print);
}
},
// Actually onChange but this is ta workaround for a bug
connections: [
['notify::text', (entry) => { // This is when you type
const isAction = entry.text.startsWith('>');
resultsBox.get_children().forEach(ch => ch.destroy());
//check empty if so then dont do stuff
if (entry.text == '') {
resultsRevealer.set_reveal_child(false);
overviewRevealer.set_reveal_child(true);
entryPromptRevealer.set_reveal_child(true);
entryIconRevealer.set_reveal_child(false);
entry.toggleClassName('overview-search-box-extended', false);
searching = false;
}
else {
const text = entry.text;
resultsRevealer.set_reveal_child(true);
overviewRevealer.set_reveal_child(false);
entryPromptRevealer.set_reveal_child(false);
entryIconRevealer.set_reveal_child(true);
entry.toggleClassName('overview-search-box-extended', true);
_appSearchResults = Applications.query(text);
// Calculate
if (startsWithNumber(text)) { // Eval on typing is dangerous, this is a workaround.
try {
const fullResult = eval(text);
resultsBox.add(CalculationResultButton({ result: fullResult, text: text }));
} catch (e) {
// console.log(e);
}
}
if (isAction) { // Eval on typing is dangerous, this is a workaround.
resultsBox.add(CustomCommandButton({ text: entry.text }));
}
// Add application entries
let appsToAdd = MAX_RESULTS;
_appSearchResults.forEach(app => {
if (appsToAdd == 0) return;
resultsBox.add(DesktopEntryButton(app));
appsToAdd--;
});
// Fallbacks
// if the first word is an actual command
if (!isAction && exec(`bash -c "command -v ${text.split(' ')[0]}"`) != '') {
resultsBox.add(ExecuteCommandButton({ command: entry.text, terminal: entry.text.startsWith('sudo') }));
}
// Add fallback: search
resultsBox.add(SearchButton({ text: entry.text }));
resultsBox.show_all();
searching = true;
}
}]
],
});
return Widget.Box({
vertical: true,
children: [
clickOutsideToClose,
Widget.Box({
halign: 'center',
children: [
entry,
Widget.Box({
className: 'overview-search-icon-box',
setup: box => box.pack_start(entryPromptRevealer, true, true, 0),
}),
entryIcon,
]
}),
overviewRevealer,
resultsRevealer,
],
connections: [
[App, (_b, name, visible) => {
if (name == 'overview' && !visible) {
entryPromptRevealer.child.label = searchPromptTexts[Math.floor(Math.random() * searchPromptTexts.length)];
resultsBox.children = [];
entry.set_text('');
}
}],
],
});
};

View File

@@ -0,0 +1,148 @@
import { Widget, Utils, Service } from '../imports.js';
import Bluetooth from 'resource:///com/github/Aylur/ags/service/bluetooth.js';
import Network from 'resource:///com/github/Aylur/ags/service/network.js';
const { execAsync, exec } = Utils;
import { BluetoothIndicator, NetworkIndicator } from "./statusicons.js";
import { setupCursorHover } from "./lib/cursorhover.js";
import { MaterialIcon } from './lib/materialicon.js';
export const ToggleIconWifi = (props = {}) => Widget.Button({
className: 'txt-small sidebar-iconbutton',
tooltipText: 'Wifi | Right-click to configure',
onClicked: Network.toggleWifi,
onSecondaryClickRelease: () => {
execAsync(['bash', '-c', 'XDG_CURRENT_DESKTOP="gnome" gnome-control-center wifi', '&']);
},
child: NetworkIndicator(),
connections: [
[Network, button => {
button.toggleClassName('sidebar-button-active', Network.wifi?.internet == 'connected' || Network.wired?.internet == 'connected')
}],
[Network, button => {
button.tooltipText = (`${Network.wifi?.ssid} | Right-click to configure` || 'Unknown');
}],
],
setup: (button) => setupCursorHover(button),
...props,
});
export const ToggleIconBluetooth = (props = {}) => Widget.Button({
className: 'txt-small sidebar-iconbutton',
tooltipText: 'Bluetooth | Right-click to configure',
onClicked: () => { // Provided service doesn't work hmmm
const status = Bluetooth?.enabled;
if (status) {
exec('rfkill block bluetooth');
}
else {
exec('rfkill unblock bluetooth');
}
},
onSecondaryClickRelease: () => {
execAsync(['bash', '-c', 'XDG_CURRENT_DESKTOP="gnome" gnome-control-center bluetooth', '&']);
},
child: BluetoothIndicator(),
connections: [
[Bluetooth, button => {
button.toggleClassName('sidebar-button-active', Bluetooth?.enabled)
}],
],
setup: (button) => setupCursorHover(button),
...props,
});
export const HyprToggleIcon = (icon, name, hyprlandConfigValue, props = {}) => Widget.Button({
className: 'txt-small sidebar-iconbutton',
tooltipText: `${name}`,
onClicked: (button) => {
// Set the value to 1 - value
Utils.execAsync(`hyprctl -j getoption ${hyprlandConfigValue}`).then((result) => {
const currentOption = JSON.parse(result).int;
execAsync(['bash', '-c', `hyprctl keyword ${hyprlandConfigValue} ${1 - currentOption} &`]).catch(print);
button.toggleClassName('sidebar-button-active', currentOption == 0);
}).catch(print);
},
child: MaterialIcon(icon, 'norm', { halign: 'center' }),
setup: button => {
button.toggleClassName('sidebar-button-active', JSON.parse(Utils.exec(`hyprctl -j getoption ${hyprlandConfigValue}`)).int == 1);
setupCursorHover(button);
},
...props,
})
export const ModuleNightLight = (props = {}) => Widget.Button({
className: 'txt-small sidebar-iconbutton',
tooltipText: 'Night Light',
onClicked: (button) => {
// Set the value to 1 - value
const shaderPath = JSON.parse(exec('hyprctl -j getoption decoration:screen_shader')).str;
if (shaderPath != "[[EMPTY]]" && shaderPath != "") {
execAsync(['bash', '-c', `hyprctl keyword decoration:screen_shader ''`]).catch(print);
button.toggleClassName('sidebar-button-active', false);
}
else {
execAsync(['bash', '-c', `hyprctl keyword decoration:screen_shader ~/.config/hypr/shaders/extradark.frag`]).catch(print);
button.toggleClassName('sidebar-button-active', true);
}
},
child: MaterialIcon('nightlight', 'norm'),
setup: (button) => setupCursorHover(button),
...props,
})
export const ModuleEditIcon = (props = {}) => Widget.Button({ // TODO: Make this work
...props,
className: 'txt-small sidebar-iconbutton',
onClicked: () => {
execAsync(['bash', '-c', 'XDG_CURRENT_DESKTOP="gnome" gnome-control-center', '&']);
App.toggleWindow('sideright');
},
child: MaterialIcon('edit', 'norm'),
setup: button => {
setupCursorHover(button);
}
})
export const ModuleReloadIcon = (props = {}) => Widget.Button({
...props,
className: 'txt-small sidebar-iconbutton',
tooltipText: 'Reload Hyprland',
onClicked: () => {
execAsync(['bash', '-c', 'hyprctl reload &']);
App.toggleWindow('sideright');
},
child: MaterialIcon('refresh', 'norm'),
setup: button => {
setupCursorHover(button);
}
})
export const ModuleSettingsIcon = (props = {}) => Widget.Button({
...props,
className: 'txt-small sidebar-iconbutton',
tooltipText: 'Open Settings',
onClicked: () => {
execAsync(['bash', '-c', 'XDG_CURRENT_DESKTOP="gnome" gnome-control-center', '&']);
App.toggleWindow('sideright');
},
child: MaterialIcon('settings', 'norm'),
setup: button => {
setupCursorHover(button);
}
})
export const ModulePowerIcon = (props = {}) => Widget.Button({
...props,
className: 'txt-small sidebar-iconbutton',
tooltipText: 'Session',
onClicked: () => {
App.toggleWindow('session');
},
child: MaterialIcon('power_settings_new', 'norm'),
setup: button => {
setupCursorHover(button);
}
})

View File

@@ -0,0 +1,44 @@
import { App, Service, Utils, Widget } from '../imports.js';
import Audio from 'resource:///com/github/Aylur/ags/service/audio.js';
import Mpris from 'resource:///com/github/Aylur/ags/service/mpris.js';
const { exec, execAsync, CONFIG_DIR } = Utils;
import Indicator from '../scripts/indicator.js';
import { StatusIcons } from "./statusicons.js";
import { RoundedCorner } from "./lib/roundedcorner.js";
import { Tray } from "./tray.js";
export const ModuleRightSpace = () => Widget.EventBox({
onScrollUp: () => {
if (Audio.speaker == null) return;
Audio.speaker.volume += 0.03;
Indicator.popup(1);
},
onScrollDown: () => {
if (Audio.speaker == null) return;
Audio.speaker.volume -= 0.03;
Indicator.popup(1);
},
onPrimaryClick: () => App.toggleWindow('sideright'),
onSecondaryClick: () => Mpris.getPlayer('')?.next(),
onMiddleClick: () => Mpris.getPlayer('')?.playPause(),
child: Widget.Box({
homogeneous: false,
children: [
Widget.Box({
hexpand: true,
className: 'spacing-h-5 txt',
children: [
Widget.Box({
hexpand: true,
className: 'spacing-h-15 txt',
setup: box => {
box.pack_end(StatusIcons(), false, false, 0);
box.pack_end(Tray(), false, false, 0);
}
}),
]
}),
RoundedCorner('topright', { className: 'corner-black' })
]
})
});

View File

@@ -0,0 +1,144 @@
// This is for the cool memory indicator on the sidebar
// For the right pill of the bar, see system.js
const { Gdk, Gtk } = imports.gi;
const GObject = imports.gi.GObject;
const Lang = imports.lang;
import { App, Service, Utils, Widget } from '../imports.js';
const { exec, execAsync } = Utils;
const SessionButton = (name, icon, command, props = {}) => {
const buttonDescription = Widget.Revealer({
valign: 'end',
transitionDuration: 200,
transition: 'slide_down',
revealChild: false,
child: Widget.Label({
className: 'txt-smaller session-button-desc',
label: name,
}),
});
return Widget.Button({
onClicked: command,
className: 'session-button',
child: Widget.Overlay({
className: 'session-button-box',
child: Widget.Label({
vexpand: true,
className: 'icon-material',
label: icon,
}),
overlays: [
buttonDescription,
]
}),
onHover: (button) => {
const display = Gdk.Display.get_default();
const cursor = Gdk.Cursor.new_from_name(display, 'pointer');
button.get_window().set_cursor(cursor);
buttonDescription.revealChild = true;
},
onHoverLost: (button) => {
const display = Gdk.Display.get_default();
const cursor = Gdk.Cursor.new_from_name(display, 'default');
button.get_window().set_cursor(cursor);
buttonDescription.revealChild = false;
},
connections: [
['focus-in-event', (self) => {
buttonDescription.revealChild = true;
self.toggleClassName('session-button-focused', true);
}],
['focus-out-event', (self) => {
buttonDescription.revealChild = false;
self.toggleClassName('session-button-focused', false);
}],
],
...props,
});
}
export default () => {
// lock, logout, sleep
const lockButton = SessionButton('Lock', 'lock', () => { App.closeWindow('session'); execAsync('gtklock') });
const logoutButton = SessionButton('Logout', 'logout', () => { App.closeWindow('session'); execAsync(['bash', '-c', 'loginctl terminate-user $USER']) });
const sleepButton = SessionButton('Sleep', 'sleep', () => { App.closeWindow('session'); execAsync('systemctl suspend') });
// hibernate, shutdown, reboot
const hibernateButton = SessionButton('Hibernate', 'downloading', () => { App.closeWindow('session'); execAsync('systemctl hibernate') });
const shutdownButton = SessionButton('Shutdown', 'power_settings_new', () => { App.closeWindow('session'); execAsync('systemctl poweroff') });
const rebootButton = SessionButton('Reboot', 'restart_alt', () => { App.closeWindow('session'); execAsync('systemctl reboot') });
const cancelButton = SessionButton('Cancel', 'close', () => App.closeWindow('session'), { className: 'session-button-cancel' });
return Widget.Box({
className: 'session-bg',
style: `
min-width: ${SCREEN_WIDTH * 2}px;
min-height: ${SCREEN_HEIGHT * 2}px;
`, // Hack to draw over reserved bar space
vertical: true,
children: [
Widget.EventBox({
onPrimaryClick: () => App.closeWindow('session'),
onSecondaryClick: () => App.closeWindow('session'),
onMiddleClick: () => App.closeWindow('session'),
}),
Widget.Box({
halign: 'center',
vexpand: true,
vertical: true,
children: [
Widget.Box({
valign: 'center',
vertical: true,
className: 'spacing-v-15',
children: [
Widget.Box({
vertical: true,
style: 'margin-bottom: 0.682rem;',
children: [
Widget.Label({
className: 'txt-title txt',
label: 'Session',
}),
Widget.Label({
justify: Gtk.Justification.CENTER,
className: 'txt-small txt',
label: 'Use arrow keys to navigate.\nEnter to select, Esc to cancel.'
}),
]
}),
Widget.Box({
halign: 'center',
className: 'spacing-h-15',
children: [ // lock, logout, sleep
lockButton,
logoutButton,
sleepButton,
]
}),
Widget.Box({
halign: 'center',
className: 'spacing-h-15',
children: [ // hibernate, shutdown, reboot
hibernateButton,
shutdownButton,
rebootButton,
]
}),
Widget.Box({
halign: 'center',
className: 'spacing-h-15',
children: [ // hibernate, shutdown, reboot
cancelButton,
]
}),
]
})
]
})
],
connections: [
[App, (_b, name, visible) => {
if (visible) lockButton.grab_focus(); // Lock is the default option
}],
],
});
}

View File

@@ -0,0 +1,75 @@
const { Gdk, Gtk } = imports.gi;
import { App, Service, Utils, Widget } from '../imports.js';
import Applications from 'resource:///com/github/Aylur/ags/service/applications.js';
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
const { execAsync, exec } = Utils;
const { Box, EventBox, Button, Label, Scrollable } = Widget;
const CLIPBOARD_SHOWN_ENTRIES = 20;
const ClipboardItems = () => {
return Box({
vertical: true,
className: 'spacing-v-5',
connections: [
[App, (box, name, visible) => {
if (name != 'sideleft')
return;
let clipboardContents = exec('cliphist list'); // Output is lines like this: 1000 copied text
clipboardContents = clipboardContents.split('\n');
// console.log(clipboardContents);
// console.log(`bash -c 'echo "${clipboardContents[0]}" | sed "s/ /\\t/" | cliphist decode'`);
// console.log(exec(`bash -c 'echo "${clipboardContents[0]}" | sed "s/ /\\t/" | cliphist decode'`));
box.children = clipboardContents.map((text, i) => {
if (i >= CLIPBOARD_SHOWN_ENTRIES) return;
return Button({
onClicked: () => {
print(`bash` + `-c` + `echo "${clipboardContents[i]}" | sed "s/ /\\\t/" | cliphist decode | wl-copy`);
execAsync(`bash`, `-c`, `echo "${clipboardContents[i]}" | sed "s/ /\\\t/" | cliphist decode | wl-copy`).catch(print);
App.closeWindow('sideleft');
},
className: 'sidebar-clipboard-item',
child: Box({
children: [
Label({
label: text,
className: 'txt-small',
truncate: 'end',
})
]
})
})
});
}]
]
});
}
export default () => Box({
vertical: true,
children: [
EventBox({
onPrimaryClick: () => App.closeWindow('sideleft'),
onSecondaryClick: () => App.closeWindow('sideleft'),
onMiddleClick: () => App.closeWindow('sideleft'),
}),
ClipboardItems(),
// Box({
// vertical: true,
// vexpand: true,
// className: 'sidebar-left',
// children: [
// Widget.Box({
// className: 'spacing-v-5',
// children: [
// ClipboardItems(),
// ]
// })
// ],
// }),
]
});

View File

@@ -0,0 +1,109 @@
const { Gdk, Gtk } = imports.gi;
import { Utils, Widget } from '../imports.js';
const { execAsync, exec } = Utils;
const { Box, EventBox } = Widget;
import {
ToggleIconBluetooth, ToggleIconWifi, HyprToggleIcon, ModuleNightLight,
ModuleEditIcon, ModuleReloadIcon, ModuleSettingsIcon, ModulePowerIcon
} from "./quicktoggles.js";
import ModuleNotificationList from "./notificationlist.js";
import { ModuleMusicControls } from "./musiccontrols.js";
import { ModuleCalendar } from "./calendar.js";
const NUM_OF_TOGGLES_PER_LINE = 5;
const togglesFlowBox = Widget({
type: Gtk.FlowBox,
className: 'sidebar-group spacing-h-10',
setup: (self) => {
self.set_max_children_per_line(NUM_OF_TOGGLES_PER_LINE);
self.add(ToggleIconWifi({ hexpand: 'true' }));
self.add(ToggleIconBluetooth({ hexpand: 'true' }));
self.add(HyprToggleIcon('mouse', 'Raw input', 'input:force_no_accel', { hexpand: 'true' }));
self.add(HyprToggleIcon('front_hand', 'No touchpad while typing', 'input:touchpad:disable_while_typing', { hexpand: 'true' }));
self.add(ModuleNightLight({ hexpand: 'true' }));
// Setup flowbox rearrange
self.connect('child-activated', (self, child) => {
if (child.get_index() === 0) {
self.reorder_child(child, self.get_children().length - 1);
} else {
self.reorder_child(child, 0);
}
});
}
})
const togglesBox = Widget.Box({
className: 'sidebar-group spacing-h-10',
children: [
ToggleIconWifi({ hexpand: 'true' }),
ToggleIconBluetooth({ hexpand: 'true' }),
HyprToggleIcon('mouse', 'Raw input', 'input:force_no_accel', { hexpand: 'true' }),
HyprToggleIcon('front_hand', 'No touchpad while typing', 'input:touchpad:disable_while_typing', { hexpand: 'true' }),
ModuleNightLight({ hexpand: 'true' }),
]
})
export default () => Box({
// vertical: true,
vexpand: true,
hexpand: true,
children: [
EventBox({
onPrimaryClick: () => App.closeWindow('sideright'),
onSecondaryClick: () => App.closeWindow('sideright'),
onMiddleClick: () => App.closeWindow('sideright'),
}),
Box({
vertical: true,
vexpand: true,
className: 'sidebar-right',
children: [
Box({
vertical: true,
vexpand: true,
className: 'spacing-v-15',
children: [
Box({
vertical: true,
className: 'spacing-v-5',
children: [
Box({ // Header
className: 'spacing-h-5 sidebar-group-invisible-morehorizpad',
children: [
Widget.Label({
className: 'txt-title txt',
connections: [[5000, label => {
execAsync([`date`, "+%H:%M"]).then(timeString => {
label.label = timeString;
}).catch(print);
}]],
}),
Widget.Label({
halign: 'center',
className: 'txt-small txt',
connections: [[5000, label => {
execAsync(['bash', '-c', `uptime -p | sed -e 's/up //;s/ hours,/h/;s/ minutes/m/'`]).then(upTimeString => {
label.label = `• uptime ${upTimeString}`;
}).catch(print);
}]],
}),
Widget.Box({ hexpand: true }),
// ModuleEditIcon({ halign: 'end' }), // TODO: Make this work
ModuleReloadIcon({ halign: 'end' }),
ModuleSettingsIcon({ halign: 'end' }),
ModulePowerIcon({ halign: 'end' }),
]
}),
// togglesFlowBox,
togglesBox,
]
}),
ModuleNotificationList({ vexpand: true, }),
ModuleCalendar(),
]
}),
],
}),
]
});

View File

@@ -0,0 +1,93 @@
import { Service, Utils, Widget } from '../imports.js';
const { exec, execAsync } = Utils;
import Audio from 'resource:///com/github/Aylur/ags/service/audio.js';
import Battery from 'resource:///com/github/Aylur/ags/service/battery.js';
import Bluetooth from 'resource:///com/github/Aylur/ags/service/bluetooth.js';
import Network from 'resource:///com/github/Aylur/ags/service/network.js';
export const BluetoothIndicator = () => Widget.Stack({
transition: 'slide_up_down',
items: [
['true', Widget.Label({ className: 'txt-norm icon-material', label: 'bluetooth' })],
['false', Widget.Label({ className: 'txt-norm icon-material', label: 'bluetooth_disabled' })],
],
connections: [[Bluetooth, stack => { stack.shown = String(Bluetooth.enabled); }]],
});
const NetworkWiredIndicator = () => Widget.Stack({
transition: 'slide_up_down',
items: [
['unknown', Widget.Label({ className: 'txt-norm icon-material', label: 'wifi_off' })],
['disconnected', Widget.Label({ className: 'txt-norm icon-material', label: 'signal_wifi_off' })],
['disabled', Widget.Label({ className: 'txt-norm icon-material', label: 'signal_wifi_statusbar_not_connected' })],
['connected', Widget.Label({ className: 'txt-norm icon-material', label: 'lan' })],
['connecting', Widget.Label({ className: 'txt-norm icon-material', label: 'signal_wifi_0_bar' })],
],
connections: [[Network, stack => {
if (!Network.wired)
return;
const { internet } = Network.wired;
if (internet === 'connected' || internet === 'connecting')
stack.shown = internet;
if (Network.connectivity !== 'full')
stack.shown = 'disconnected';
stack.shown = 'disabled';
}]],
});
const NetworkWifiIndicator = () => Widget.Stack({
transition: 'slide_up_down',
items: [
['disabled', Widget.Label({ className: 'txt-norm icon-material', label: 'wifi_off' })],
['disconnected', Widget.Label({ className: 'txt-norm icon-material', label: 'signal_wifi_off' })],
['connecting', Widget.Label({ className: 'txt-norm icon-material', label: 'signal_wifi_statusbar_not_connected' })],
['4', Widget.Label({ className: 'txt-norm icon-material', label: 'signal_wifi_4_bar' })],
['3', Widget.Label({ className: 'txt-norm icon-material', label: 'network_wifi_3_bar' })],
['2', Widget.Label({ className: 'txt-norm icon-material', label: 'network_wifi_2_bar' })],
['1', Widget.Label({ className: 'txt-norm icon-material', label: 'network_wifi_1_bar' })],
['0', Widget.Label({ className: 'txt-norm icon-material', label: 'signal_wifi_0_bar' })],
],
connections: [[Network,
stack => {
if (!Network.wifi)
return;
const { internet, enabled, strength } = Network.wifi;
if (internet == 'connected') {
stack.shown = String(Math.ceil(strength / 25));
}
else {
stack.shown = 'disconnected'
}
}
]],
});
export const NetworkIndicator = () => Widget.Stack({
transition: 'slide_up_down',
items: [
['wifi', NetworkWifiIndicator()],
['wired', NetworkWiredIndicator()],
],
connections: [[Network, stack => {
const primary = Network.primary || 'wifi';
stack.shown = primary;
}]],
});
export const StatusIcons = (props = {}) => Widget.Box({
...props,
children: [Widget.EventBox({
child: Widget.Box({
className: 'spacing-h-15',
children: [
BluetoothIndicator(),
NetworkIndicator(),
]
})
})]
});

View File

@@ -0,0 +1,108 @@
// This is for the cool memory indicator on the sidebar
// For the right pill of the bar, see system.js
const { Gdk, Gtk } = imports.gi;
const GObject = imports.gi.GObject;
const Lang = imports.lang;
import { App, Service, Utils, Widget } from '../imports.js';
import Bluetooth from 'resource:///com/github/Aylur/ags/service/bluetooth.js';
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
import Network from 'resource:///com/github/Aylur/ags/service/network.js';
const { execAsync, exec } = Utils;
import { CircularProgress } from "./lib/circularprogress.js";
import { MaterialIcon } from "./lib/materialicon.js";
let cpuUsageQueue = [];
const CPU_HISTORY_LENGTH = 10;
export const ModuleSysInfo = (props = {}) => {
const swapCircle = Widget({
type: CircularProgress,
className: 'sidebar-memory-swap-circprog',
valign: 'center',
});
const ramCircle = Widget({
type: CircularProgress,
className: 'sidebar-memory-ram-circprog margin-right-10', // margin right 10 here cuz overlay can't have margins itself
valign: 'center',
});
const cpuCircle = Widget.Box({
children: [Widget({
type: CircularProgress,
className: 'sidebar-cpu-circprog margin-right-10', // margin right 10 here cuz overlay can't have margins itself
valign: 'center',
})]
});
const memoryCircles = Widget.Box({
homogeneous: true,
children: [Widget.Overlay({
child: ramCircle,
overlays: [
swapCircle,
]
})]
});
const ramText = Widget.Label({
halign: 'start', xalign: 0,
className: 'txt txt-small',
});
const swapText = Widget.Label({
halign: 'start', xalign: 0,
className: 'txt txt-small',
});
const memoryText = Widget.Box({
vertical: true,
valign: 'center',
className: 'spacing-v--5',
children: [
Widget.Box({
className: 'spacing-h-5',
children: [
MaterialIcon('memory', 'large', { setup: icon => icon.toggleClassName('txt', true) }),
ramText
]
}),
Widget.Box({
className: 'spacing-h-5',
children: [
MaterialIcon('swap_horiz', 'large', { setup: icon => icon.toggleClassName('txt', true) }),
swapText
]
}),
]
});
return Widget.Box({
...props,
className: 'sidebar-group-nopad',
children: [Widget.Scrollable({
hexpand: true,
vscroll: 'never',
hscroll: 'automatic',
child: Widget.Box({
className: 'sidebar-sysinfo-grouppad spacing-h--5',
children: [
memoryCircles,
memoryText,
// cpuCircle,
// maybe make cpu a graph?
],
connections: [
[3000, () => {
// Get memory info
const ramString = exec(`bash -c 'free -h --si | rg "Mem:"'`);
const [ramTotal, ramUsed] = ramString.split(/\s+/).slice(1, 3);
const ramPerc = Number(exec(`bash -c "printf '%.1f' \\\"$(free -m | rg Mem | awk '{print ($3/$2)*100}')\\\""`));
const swapString = exec(`bash -c 'free -h --si | rg "Swap:"'`);
const [swapTotal, swapUsed] = swapString.split(/\s+/).slice(1, 3);
const swapPerc = Number(exec(`bash -c "printf '%.1f' \\\"$(free -m | rg Swap | awk '{print ($3/$2)*100}')\\\""`));
// const cpuPerc = parseFloat(exec(`bash -c "top -bn1 | grep 'Cpu(s)' | awk '{print $2 + $4}'"`));
// Set circular progress (font size cuz hack for anims)
ramCircle.style = `font-size: ${ramPerc}px;`
swapCircle.style = `font-size: ${swapPerc}px;`
ramText.label = `${ramUsed} / ${ramTotal}`;
swapText.label = `${swapUsed} / ${swapTotal}`;
}]
]
})
})]
});
};

View File

@@ -0,0 +1,89 @@
// This is for the right pill of the bar.
// For the cool memory indicator on the sidebar, see sysinfo.js
import { Service, Utils, Widget } from '../imports.js';
const { exec, execAsync } = Utils;
import Battery from 'resource:///com/github/Aylur/ags/service/battery.js';
export const ModuleSystem = () => Widget.EventBox({
onScrollUp: () => execAsync('hyprctl dispatch workspace -1'),
onScrollDown: () => execAsync('hyprctl dispatch workspace +1'),
child: Widget.Box({
className: 'bar-group-margin bar-sides',
children: [
Widget.Box({
className: 'bar-group bar-group-standalone bar-group-pad-system spacing-h-15',
children: [
Widget.Box({ // Clock
valign: 'center',
className: 'spacing-h-5',
children: [
Widget.Label({
className: 'bar-clock',
connections: [[5000, label => {
execAsync([`date`, "+%H:%M"]).then(timeString => {
label.label = timeString;
}).catch(print);
}]],
}),
Widget.Label({
className: 'txt-norm txt',
label: '•',
}),
Widget.Label({
className: 'txt-smallie txt',
connections: [[5000, label => {
execAsync([`date`, "+%A, %d/%m"]).then(dateString => {
label.label = dateString;
}).catch(print);
}]],
}),
],
}),
/*Widget.Box({ // Battery
valign: 'center',
hexpand: true,
className: 'spacing-h-5 bar-batt',
connections: [[Battery, box => {
box.toggleClassName('bar-batt-low', Battery.percent <= 20);
box.toggleClassName('bar-batt-full', Battery.charged);
}]],
children: [
Widget.Label({
className: 'bar-batt-percentage',
connections: [[Battery, label => {
label.label = `${Battery.percent}`;
}]],
}),
Widget.ProgressBar({
valign: 'center',
hexpand: true,
className: 'bar-prog-batt',
connections: [[Battery, progress => {
progress.value = Math.abs(Battery.percent / 100); // battery could be initially negative wtf
progress.toggleClassName('bar-prog-batt-low', Battery.percent <= 20);
progress.toggleClassName('bar-prog-batt-full', Battery.charged);
}]],
}),
Widget.Revealer({
transitionDuration: 150,
revealChild: false,
transition: 'slide_left',
child: Widget.Box({
valign: 'center',
className: 'bar-batt-chargestate-charging',
connections: [[Battery, box => {
box.toggleClassName('bar-batt-chargestate-low', Battery.percent <= 20);
box.toggleClassName('bar-batt-chargestate-full', Battery.charged);
}]],
}),
connections: [[Battery, revealer => {
revealer.revealChild = Battery.charging;
}]],
}),
],
}),]*/
],
}),
]
})
});

View File

@@ -0,0 +1,71 @@
import { Service, Widget } from '../imports.js';
import SystemTray from 'resource:///com/github/Aylur/ags/service/systemtray.js';
const { Box, Icon, Button, Revealer } = Widget;
const { Gravity } = imports.gi.Gdk;
const revealerDuration = 200;
const SysTrayItem = item => Button({
className: 'bar-systray-item',
child: Icon({
halign: 'center',
size: 16,
binds: [['icon', item, 'icon']]
}),
binds: [['tooltipMarkup', item, 'tooltipMarkup']],
// setup: btn => {
// const id = item.menu.connect('popped-up', menu => {
// menu.disconnect(id);
// });
// },
onClicked: btn => item.menu.popup_at_widget(btn, Gravity.SOUTH, Gravity.NORTH, null),
onSecondaryClick: btn => item.menu.popup_at_widget(btn, Gravity.SOUTH, Gravity.NORTH, null),
});
export const Tray = (props = {}) => {
const trayContent = Box({
valign: 'center',
className: 'bar-systray bar-group',
properties: [
['items', new Map()],
['onAdded', (box, id) => {
const item = SystemTray.getItem(id);
if (!item) return;
item.menu.className = 'menu';
if (box._items.has(id) || !item)
return;
const widget = SysTrayItem(item);
box._items.set(id, widget);
box.pack_start(widget, false, false, 0);
box.show_all();
if (box._items.size === 1)
trayRevealer.revealChild = true;
}],
['onRemoved', (box, id) => {
if (!box._items.has(id))
return;
box._items.get(id).destroy();
box._items.delete(id);
if (box._items.size === 0)
trayRevealer.revealChild = false;
}],
],
connections: [
[SystemTray, (box, id) => box._onAdded(box, id), 'added'],
[SystemTray, (box, id) => box._onRemoved(box, id), 'removed'],
],
});
const trayRevealer = Widget.Revealer({
revealChild: false,
transition: 'slide_left',
transitionDuration: revealerDuration,
child: trayContent,
});
return Box({
...props,
children: [
trayRevealer,
]
});
}

View File

@@ -0,0 +1,97 @@
import { App, Service, Utils, Widget } from '../imports.js';
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
import { deflisten } from '../scripts/scripts.js';
const WORKSPACE_SIDE_PAD = 0.546; // rem
const NUM_OF_WORKSPACES = 10;
let lastWorkspace = 0;
const activeWorkspaceIndicator = Widget.Box({
// style: 'margin-left: -1px;',
children: [
Widget.Box({
valign: 'center',
halign: 'start',
className: 'bar-ws-active-box',
connections: [
[Hyprland.active.workspace, (box) => {
const ws = Hyprland.active.workspace.id;
box.setStyle(`
margin-left: -${1.772 * (10 - ws + 1)}rem;
`);
lastWorkspace = ws;
}],
],
children: [
Widget.Label({
valign: 'center',
className: 'bar-ws-active',
label: ``,
})
]
})
]
});
export const ModuleWorkspaces = () => Widget.EventBox({
onScrollUp: () => Utils.execAsync(['bash', '-c', 'hyprctl dispatch workspace -1 &']),
onScrollDown: () => Utils.execAsync(['bash', '-c', 'hyprctl dispatch workspace +1 &']),
onMiddleClickRelease: () => App.toggleWindow('overview'),
onSecondaryClickRelease: () => App.toggleWindow('osk'),
child: Widget.Box({
homogeneous: true,
className: 'bar-ws-width',
children: [
Widget.Overlay({
passThrough: true,
child: Widget.Box({
homogeneous: true,
className: 'bar-group-center',
children: [Widget.Box({
className: 'bar-group-standalone bar-group-pad',
})]
}),
overlays: [
Widget.Box({
style: `
padding: 0rem ${WORKSPACE_SIDE_PAD}rem;
`,
children: [
Widget.Box({
halign: 'center',
// homogeneous: true,
children: Array.from({ length: NUM_OF_WORKSPACES }, (_, i) => i + 1).map(i => Widget.Button({
onPrimaryClick: () => Utils.execAsync(['bash', '-c', `hyprctl dispatch workspace ${i} &`]).catch(print),
child: Widget.Label({
valign: 'center',
label: `${i}`,
className: 'bar-ws txt',
}),
})),
connections: [
[Hyprland, (box) => { // TODO: connect to the right signal so that it doesn't update too much
// console.log('update');
const kids = box.children;
kids.forEach((child, i) => {
child.child.toggleClassName('bar-ws-occupied', false);
child.child.toggleClassName('bar-ws-occupied-left', false);
child.child.toggleClassName('bar-ws-occupied-right', false);
child.child.toggleClassName('bar-ws-occupied-left-right', false);
});
const occupied = Array.from({ length: NUM_OF_WORKSPACES }, (_, i) => Hyprland.getWorkspace(i + 1)?.windows > 0);
for (let i = 0; i < occupied.length; i++) {
if (!occupied[i]) continue;
const child = kids[i];
child.child.toggleClassName(`bar-ws-occupied${!occupied[i - 1] ? '-left' : ''}${!occupied[i + 1] ? '-right' : ''}`, true);
}
}],
],
}),
activeWorkspaceIndicator,
]
})
]
})
]
})
});