Files
hyprland/config/ags/modules/applauncher.ts
T
2024-06-26 00:10:21 +05:45

180 lines
4.8 KiB
TypeScript

const { query } = await Service.import("applications")
const encoder = new TextEncoder();
const decoder = new TextDecoder();
const Gio = imports.gi.Gio;
const { Gtk } = imports.gi;
const GLib = imports.gi.GLib;
const WINDOW_NAME = "applauncher"
import Box from 'types/widgets/box';
import popupwindow from './misc/popupwindow.ts';
import { Application } from 'types/service/applications';
const LAUNCH_COUNT_FILE = Gio.File.new_for_path(
GLib.build_filenamev([GLib.get_home_dir(), '.cache', 'launch_counts.json'])
);
function readLaunchCounts() {
try {
if (!LAUNCH_COUNT_FILE.query_exists(null)) {
return {};
}
const [, contents] = LAUNCH_COUNT_FILE.load_contents(null);
return JSON.parse(decoder.decode(contents));
} catch (error) {
return {};
}
}
function writeLaunchCounts(launchCounts: number) {
const data = JSON.stringify(launchCounts, null, 2);
LAUNCH_COUNT_FILE.replace_contents(
encoder.encode(data),
null,
false,
Gio.FileCreateFlags.REPLACE_DESTINATION,
null
);
}
function incrementLaunchCount(appName: string) {
const launchCounts = readLaunchCounts();
launchCounts[appName] = (launchCounts[appName] || 0) + 1;
writeLaunchCounts(launchCounts);
}
function sortApplicationsByLaunchCount(applications: Box<any, any>[]) {
const launchCounts = readLaunchCounts()
return applications.sort((a: Box<any, any>, b: Box<any, any>) => {
const countA = launchCounts[a.attribute.app.name] || 0
const countB = launchCounts[b.attribute.app.name] || 0
return countB - countA
})
}
function AppItem(app: Application): Box<any, any> {
let clickCount = 0;
const button = Widget.Button({
class_name: "application_container",
child: Widget.Box({
children: [
Widget.Icon({
class_name: "application_icon",
// @ts-ignore
icon: Utils.lookUpIcon(app.icon_name) ? app.icon_name : "image-missing",
size: 42,
}),
Widget.Label({
class_name: "application_label",
label: app.name,
xalign: 0,
vpack: "center",
truncate: "end",
}),
]
})
})
button.connect('clicked', () => {
clickCount++;
if (clickCount === 2) {
incrementLaunchCount(app.name);
App.closeWindow(WINDOW_NAME);
app.launch();
clickCount = 0;
}
});
button.connect('focus-out-event', () => {
clickCount = 0;
});
return Widget.Box({
attribute: { app },
orientation: Gtk.Orientation.VERTICAL,
children: [
button,
Widget.Separator({
class_name: "application_divider",
orientation: Gtk.Orientation.HORIZONTAL
})
],
})
};
const Applauncher = ({ width = 500, height = 500, spacing = 12 }) => {
let applications: Box<any, any>[];
const list = Widget.Box({
vertical: true,
spacing,
})
function repopulate() {
applications = query("").map(AppItem)
applications = sortApplicationsByLaunchCount(applications)
list.children = applications
}
repopulate()
const entry = Widget.Entry({
hexpand: true,
class_name: "applauncher_entry",
placeholder_text: "Search",
on_accept: () => {
const results = applications.filter((item) => item.visible)
if (results[0]) {
App.toggleWindow(WINDOW_NAME)
results[0].attribute.app.launch()
}
},
on_change: ({ text }) => applications.forEach(item => {
item.visible = item.attribute.app.match(text ?? "")
}),
})
return Widget.Box({
vertical: true,
css: `margin: ${spacing * 2}px;`,
class_name: "applauncher_box",
margin_top: 14,
margin_left: 14,
children: [
entry,
Widget.Scrollable({
hscroll: "never",
css: `min-width: ${width}px;`
+ `min-height: ${height}px;`,
child: list,
}),
],
setup: self => self.hook(App, (_, windowName, visible) => {
if (windowName !== WINDOW_NAME)
return
if (visible) {
repopulate()
entry.text = ""
}
}),
})
}
export const applauncher = popupwindow({
name: WINDOW_NAME,
class_name: "applauncher",
visible: false,
keymode: "exclusive",
child: Applauncher({
width: 500,
height: 500,
spacing: 0,
}),
anchor: ["bottom", "left"]
})