Initial commit

This commit is contained in:
2025-11-10 10:56:42 +01:00
parent d4895922de
commit 434d15169a
7 changed files with 4379 additions and 4268 deletions

6
app.ts
View File

@@ -6,7 +6,7 @@ import { runCLI } from 'src/services/cli/commander';
import { InitializationService } from 'src/core/initialization';
App.start({
instanceName: 'hyprpanel',
requestHandler: (request: string, res: (response: unknown) => void) => runCLI(request, res),
main: () => InitializationService.initialize(),
instanceName: 'hyprpanel-levl',
requestHandler: (request: string, res: (response: unknown) => void) => runCLI(request, res),
main: () => InitializationService.initialize(),
});

8484
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,31 +1,31 @@
{
"name": "hyprpanel",
"version": "1.0.0",
"description": "A customizable panel built for Hyprland.",
"main": "app.ts",
"scripts": {
"lint": "eslint --config .eslintrc.json .",
"lint:fix": "eslint --config .eslintrc.json . --fix",
"format": "prettier --write 'modules/**/*.ts'",
"knip": "knip"
},
"keywords": [],
"author": "",
"license": "MIT",
"dependencies": {
"astal": "/usr/share/astal/gjs"
},
"devDependencies": {
"@types/node": "^22.15.17",
"@typescript-eslint/eslint-plugin": "^8.5.0",
"@typescript-eslint/parser": "^8.5.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.30.0",
"eslint-plugin-prettier": "^5.2.1",
"knip": "^5.55.1",
"prettier": "^3.3.3",
"tsconfig-paths": "^4.2.0",
"typescript": "5.7.3"
}
"name": "hyprpanel",
"version": "1.0.0",
"description": "A customizable panel built for Hyprland.",
"main": "app.ts",
"scripts": {
"lint": "eslint --config .eslintrc.json .",
"lint:fix": "eslint --config .eslintrc.json . --fix",
"format": "prettier --write 'modules/**/*.ts'",
"knip": "knip"
},
"keywords": [],
"author": "",
"license": "MIT",
"dependencies": {
"astal": "/usr/share/astal/gjs"
},
"devDependencies": {
"@types/node": "^22.15.17",
"@typescript-eslint/eslint-plugin": "^8.5.0",
"@typescript-eslint/parser": "^8.5.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.30.0",
"eslint-plugin-prettier": "^5.2.1",
"knip": "^5.55.1",
"prettier": "^3.3.3",
"tsconfig-paths": "^4.2.0",
"typescript": "5.7.3"
}
}

View File

@@ -5,6 +5,7 @@ import { isValidGjsColor } from 'src/lib/validation/colors';
import { AppIconOptions } from './types';
import { WorkspaceIconMap } from '../types';
import { unique } from 'src/lib/array/helpers';
import {Apps ,AstalAppsApplication} from "gi://AstalApps"
const hyprlandService = AstalHyprland.get_default();
const { monochrome, background } = options.theme.bar.buttons;
@@ -151,7 +152,13 @@ export const getAppIcon = (
return iconEntry?.[1] ?? defaultIcon;
};
let icons = workspaceClients.reduce((iconAccumulator, [clientClass, clientTitle]) => {
let icons = getAppIconNew(hyprlandService
.get_clients()
.filter((client) => client?.workspace?.id === workspaceIndex))
icons = workspaceClients.reduce((iconAccumulator, [clientClass, clientTitle]) => {
const icon = findIconForClient(clientClass, clientTitle);
if (icon !== undefined) {
@@ -160,6 +167,7 @@ export const getAppIcon = (
return iconAccumulator;
}, []);
if (icons.length) {
if (removeDuplicateIcons) {
@@ -172,6 +180,35 @@ export const getAppIcon = (
return defaultIcon;
};
// todo implement caching to make it so much faster hopefully
export function getAppIconNew(
workspaceIndex: number,
apps: AstalAppsApplication
) : string[] {
const clients = hyprlandService
.get_clients()
.filter((client) => client?.workspace?.id === workspaceIndex);
let icons = clients?.map((client) =>
apps
.filter((app: AstalHyprland.app) =>
app.wm_class == client.get_class() ||
app.wm_class == client?.initial_title ||
app.wm_class == getWMClass(client) ||
app.name == client?.initial_title)
.map((app: AstalHyprland.app) => app.icon_name).at(0) ?? "missing-symbolic"
)
return icons.length > 0 ? icons : ["application-x-executable"]
}
function getWMClass(client: AstalHyprland.Client | undefined) {
return client.initial_class.split(".").reverse().at(0)
}
/**
* Renders the class names for a workspace.
*

View File

@@ -1,5 +1,5 @@
import { initWorkspaceEvents } from './helpers/utils';
import { getAppIcon, getWsColor, renderClassnames, renderLabel } from './helpers';
import { getAppIcon, getWsColor, renderClassnames, renderLabel, getAppIconNew } from './helpers';
import { bind, Variable } from 'astal';
import AstalHyprland from 'gi://AstalHyprland?version=0.1';
import { Gtk } from 'astal/gtk3';
@@ -7,6 +7,8 @@ import { WorkspaceService } from 'src/services/workspace';
import options from 'src/configuration';
import { isPrimaryClick } from 'src/lib/events/mouse';
import { WorkspaceIconMap, ApplicationIcons } from './types';
import Apps from "gi://AstalApps"
const workspaceService = WorkspaceService.getInstance();
@@ -100,15 +102,11 @@ export const WorkspaceModule = ({ monitor }: WorkspaceModuleProps): JSX.Element
isMonitorSpecific,
monitorList,
);
const apps = new Apps.Apps({}).get_list()
return workspacesToRender.map((wsId, index) => {
const appIcons = displayApplicationIcons
? getAppIcon(wsId, appIconOncePerWorkspace, {
iconMap: applicationIconMapping,
defaultIcon: applicationIconFallback,
emptyIcon: applicationIconEmptyWorkspace,
})
: '';
const appIcons = getAppIconNew(wsId, apps);
return (
<button
@@ -119,9 +117,8 @@ export const WorkspaceModule = ({ monitor }: WorkspaceModuleProps): JSX.Element
}
}}
>
<label
valign={Gtk.Align.CENTER}
css={
<box
css={
`margin: 0rem ${0.375 * spacingValue}rem;` +
`${displayWorkspaceIcons && !matugenEnabled ? getWsColor(workspaceIconMapping, wsId, smartHighlightEnabled, monitor) : ''}`
}
@@ -134,27 +131,21 @@ export const WorkspaceModule = ({ monitor }: WorkspaceModuleProps): JSX.Element
monitor,
wsId,
)}
label={renderLabel(
displayIcons,
availableStatus,
activeStatus,
occupiedStatus,
displayApplicationIcons,
appIcons,
workspaceMaskFlag,
displayWorkspaceIcons,
workspaceIconMapping,
wsId,
index,
monitor,
)}
setup={(self) => {
const currentWsClients = clients.filter(
(client) => client?.workspace?.id === wsId,
);
self.toggleClassName('occupied', currentWsClients.length > 0);
}}
/>
}}>
{
appIcons?.map(icon =>
<icon
icon={icon || "image-missing"}
/>
)}
</box>
</button>
);
});

View File

@@ -57,7 +57,9 @@ export class WorkspaceService {
let allPotentialWorkspaces = range(totalWorkspaces || 8);
const allWorkspaceInstances = workspaceInstances ?? [];
const activeWorkspaceIds = allWorkspaceInstances.map((workspaceInstance) => workspaceInstance.id);
const activeWorkspaceIds = allWorkspaceInstances.filter(
(workspaceInstance) => !workspaceInstance.name.startsWith("special:")
).map((workspaceInstance) => workspaceInstance.id);
const monitorReferencesForActiveWorkspaces = allWorkspaceInstances.map((workspaceInstance) => {
return {
@@ -171,6 +173,9 @@ export class WorkspaceService {
const workspaceMonitorReferences = allWorkspaceInstances
.filter((workspaceInstance) => workspaceInstance !== null)
.filter(
(workspaceInstance) => !workspaceInstance.name.startsWith("special:")
)
.map((workspaceInstance) => {
return {
id: workspaceInstance.monitor?.id,
@@ -224,6 +229,9 @@ export class WorkspaceService {
.filter(
(workspaceInstance) => hyprlandService.focusedMonitor.id === workspaceInstance.monitor?.id,
)
.filter(
(workspaceInstance) => !workspaceInstance.name.startsWith("special:")
)
.map((workspaceInstance) => workspaceInstance.id);
const assignedOrOccupiedWorkspaces = activeWorkspaceIds.sort((a, b) => a - b);

View File

@@ -1,11 +1,10 @@
.workspaces {
label {
label, box {
font-size: 0.2em;
min-width: $bar-buttons-workspaces-pill-width;
min-height: $bar-buttons-workspaces-pill-height;
border-radius: $bar-buttons-workspaces-pill-radius;
transition: 300ms * 0.5;
background-color: $bar-buttons-workspaces-available;
color: $bar-buttons-workspaces-available;
&.occupied {
@@ -58,7 +57,7 @@
}
.workspace-button {
&:hover label {
&:hover label, box {
color: $bar-buttons-workspaces-hover;
&.default {