commit ec117cea004ec01ebdaa1037f150412eab988086 Author: tux Date: Sat May 31 14:00:56 2025 +0530 feat: initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..298eb4d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +@girs/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d46f767 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2025 tux + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/app.ts b/app.ts new file mode 100644 index 0000000..0ff002c --- /dev/null +++ b/app.ts @@ -0,0 +1,10 @@ +import { App } from "astal/gtk4"; +import style from "./style.scss"; +import windows from "./windows"; + +App.start({ + css: style, + main() { + windows.map((win) => App.get_monitors().map(win)); + }, +}); diff --git a/assets/avatar.png b/assets/avatar.png new file mode 100644 index 0000000..2a50528 Binary files /dev/null and b/assets/avatar.png differ diff --git a/env.d.ts b/env.d.ts new file mode 100644 index 0000000..467c0a4 --- /dev/null +++ b/env.d.ts @@ -0,0 +1,21 @@ +declare const SRC: string + +declare module "inline:*" { + const content: string + export default content +} + +declare module "*.scss" { + const content: string + export default content +} + +declare module "*.blp" { + const content: string + export default content +} + +declare module "*.css" { + const content: string + export default content +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..4ceda29 --- /dev/null +++ b/flake.lock @@ -0,0 +1,91 @@ +{ + "nodes": { + "ags": { + "inputs": { + "astal": "astal", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1744557573, + "narHash": "sha256-XAyj0iDuI51BytJ1PwN53uLpzTDdznPDQFG4RwihlTQ=", + "owner": "aylur", + "repo": "ags", + "rev": "3ed9737bdbc8fc7a7c7ceef2165c9109f336bff6", + "type": "github" + }, + "original": { + "owner": "aylur", + "repo": "ags", + "type": "github" + } + }, + "astal": { + "inputs": { + "nixpkgs": [ + "ags", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1742571008, + "narHash": "sha256-5WgfJAeBpxiKbTR/gJvxrGYfqQRge5aUDcGKmU1YZ1Q=", + "owner": "aylur", + "repo": "astal", + "rev": "dc0e5d37abe9424c53dcbd2506a4886ffee6296e", + "type": "github" + }, + "original": { + "owner": "aylur", + "repo": "astal", + "type": "github" + } + }, + "astal_2": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1748416910, + "narHash": "sha256-FEQcs58HL8Fe4i7XlqVEUwthjxwvRvgX15gTTfW17sU=", + "owner": "aylur", + "repo": "astal", + "rev": "c1bd89a47c81c66ab5fc6872db5a916c0433fb89", + "type": "github" + }, + "original": { + "owner": "aylur", + "repo": "astal", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1748026106, + "narHash": "sha256-6m1Y3/4pVw1RWTsrkAK2VMYSzG4MMIj7sqUy7o8th1o=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "063f43f2dbdef86376cc29ad646c45c46e93234c", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "ags": "ags", + "astal": "astal_2", + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..468ee2c --- /dev/null +++ b/flake.nix @@ -0,0 +1,60 @@ +{ + description = "tux's widgets for wayland"; + inputs = { + nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; + astal = { + url = "github:aylur/astal"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + ags = { + url = "github:aylur/ags"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = { + self, + nixpkgs, + astal, + ags, + }: let + system = "x86_64-linux"; + pkgs = nixpkgs.legacyPackages.${system}; + in { + packages.${system} = { + default = ags.lib.bundle { + inherit pkgs; + src = ./.; + name = "tpanel"; + entry = "app.ts"; + gtk4 = true; + + extraPackages = with ags.packages.${system}; [ + hyprland + apps + battery + ]; + }; + + astal = astal.packages.${system}; + }; + + devShells.${system} = { + default = pkgs.mkShell { + nativeBuildInputs = [ + astal.packages.${system}.default + ]; + buildInputs = [ + # includes astal3 astal4 astal-io by default + (ags.packages.${system}.default.override { + extraPackages = with ags.packages.${system}; [ + hyprland + apps + battery + ]; + }) + ]; + }; + }; + }; +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..98d7200 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,35 @@ +{ + "name": "astal-shell", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "astal-shell", + "dependencies": { + "astal": "/nix/store/8cl58ip2nasg6rdyj59cwg2f0qbixs28-astal-gjs/share/astal/gjs", + "typescript": "^5.7.3" + } + }, + "../../../../nix/store/8cl58ip2nasg6rdyj59cwg2f0qbixs28-astal-gjs/share/astal/gjs": { + "name": "astal", + "license": "LGPL-2.1" + }, + "node_modules/astal": { + "resolved": "../../../../nix/store/8cl58ip2nasg6rdyj59cwg2f0qbixs28-astal-gjs/share/astal/gjs", + "link": true + }, + "node_modules/typescript": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..74960ce --- /dev/null +++ b/package.json @@ -0,0 +1,7 @@ +{ + "name": "astal-shell", + "dependencies": { + "astal": "/nix/store/8cl58ip2nasg6rdyj59cwg2f0qbixs28-astal-gjs/share/astal/gjs", + "typescript": "^5.7.3" + } +} diff --git a/style.scss b/style.scss new file mode 100644 index 0000000..4906cc5 --- /dev/null +++ b/style.scss @@ -0,0 +1,84 @@ +@use "sass:math"; +@use "sass:list"; + +$accent: #ebdbb2; +$fg-color: #ebdbb2; +$bg-color: #282828; +$inactive-color: #807966; + +window.Bar { + background: $bg-color; + color: $fg-color; + margin: 10px; + border-radius: 10px; + padding: 10px; + + .launcher { + button { + background-color: $inactive-color; + border-radius: 10px; + transition: all 0.15s ease-out; + padding: 0px; + margin: 0px; + + &:hover { + background-color: $bg-color; + } + } + } + + .workspace-container { + background-color: $bg-color; + color: $fg-color; + border-radius: 10px; + + .workspace-button { + padding: 0px; + border: 0px; + margin: 2px; + background-color: $inactive-color; + min-height: 1em; + min-width: 1rem; + border-radius: calc(list.nth(8px, 1) * 1.5); + transition: min-width 0.15s ease-out; + + &:hover { + background-color: $fg-color; + &.occupied { + background-color: $fg-color; + } + } + + &.active { + min-width: 2rem; + min-height: 1rem; + background-color: $accent; + } + + &.occupied { + background-color: $accent; + &.active { + background-color: $accent; + } + } + } + } +} + +window.app-launcher { + background: $bg-color; + border-radius: 10px; + padding: 10px; + + .app-list { + background: $bg-color; + } + + .app-button { + border-radius: 10px; + } + + .name { + color: $fg-color; + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..a92bc43 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "experimentalDecorators": true, + "strict": true, + "target": "ES2022", + "module": "ES2022", + "moduleResolution": "Bundler", + // "checkJs": true, + // "allowJs": true, + "jsx": "react-jsx", + "jsxImportSource": "astal/gtk4", + } +} diff --git a/widget/app-launcher/index.tsx b/widget/app-launcher/index.tsx new file mode 100644 index 0000000..cba7df1 --- /dev/null +++ b/widget/app-launcher/index.tsx @@ -0,0 +1,103 @@ +import { Variable, Gio } from "astal"; +import { App, Astal, Gdk, Gtk, hook } from "astal/gtk4"; +import AstalApps from "gi://AstalApps"; +import { Picture } from "../common"; + +export const WINDOW_NAME = "app-launcher"; +const apps = new AstalApps.Apps(); +const text = Variable(""); + +const hide = () => { + App.get_window(WINDOW_NAME)?.set_visible(false); +}; + +const AppButton = ({ app }: { app: AstalApps.Application }) => { + return ( + + ); +}; + +const AppList = () => { + const appList = text((text) => apps.fuzzy_query(text)); + + return ( + + + {appList.as((list) => list.map((app) => ))} + + + ); +}; + +const AppSearch = () => { + const onEnter = () => { + apps.fuzzy_query(text.get())?.[0].launch(); + hide(); + }; + return ( + + + + + text.set(self.text)} + onActivate={onEnter} + setup={(self) => { + hook(self, App, "window-toggled", (_, win) => { + const winName = win.name; + const visible = win.visible; + + if (winName == WINDOW_NAME && visible) { + text.set(""); + self.set_text(""); + self.grab_focus(); + } + }); + }} + /> + + ); +}; + +export const AppLauncher = (gdkmonitor: Gdk.Monitor) => { + return ( + { + if (keyval === Gdk.KEY_Escape) { + App.toggle_window(WINDOW_NAME); + } + }} + > + + + + + + ); +}; diff --git a/widget/bar/battery.tsx b/widget/bar/battery.tsx new file mode 100644 index 0000000..dcab464 --- /dev/null +++ b/widget/bar/battery.tsx @@ -0,0 +1,15 @@ +import { bind } from "astal"; +import AstalBattery from "gi://AstalBattery"; + +export const Battery = () => { + const battery = AstalBattery.get_default(); + + return ( + + + + ); +}; diff --git a/widget/bar/index.tsx b/widget/bar/index.tsx new file mode 100644 index 0000000..914d8ea --- /dev/null +++ b/widget/bar/index.tsx @@ -0,0 +1,45 @@ +import { App, Astal, Gdk } from "astal/gtk4"; +import { FocusedClient, WorkspaceButton } from "./workspace"; +import { Battery } from "./battery"; +import { Launcher } from "./launcher"; + +export const WINDOW_NAME = "bar"; + +export const Bar = (gdkmonitor: Gdk.Monitor) => { + const { TOP, LEFT, RIGHT } = Astal.WindowAnchor; + + return ( + + + +
+ + + + ); +}; + +const Start = () => { + return ( + + + + + ); +}; + +const Center = () => { + return ; +}; + +const End = () => { + return ; +}; diff --git a/widget/bar/launcher.tsx b/widget/bar/launcher.tsx new file mode 100644 index 0000000..441a034 --- /dev/null +++ b/widget/bar/launcher.tsx @@ -0,0 +1,22 @@ +import { App, Gtk } from "astal/gtk4"; +import { Gio } from "astal"; +import { Picture } from "../common"; +import { WINDOW_NAME } from "../app-launcher"; + +export const Launcher = () => { + return ( + + + + ); +}; diff --git a/widget/bar/workspace.tsx b/widget/bar/workspace.tsx new file mode 100644 index 0000000..5130f02 --- /dev/null +++ b/widget/bar/workspace.tsx @@ -0,0 +1,56 @@ +import { Variable, bind } from "astal"; +import { Gtk } from "astal/gtk4"; +import { ButtonProps } from "astal/gtk4/widget"; +import AstalHyprland from "gi://AstalHyprland"; + +type WsButtonProps = ButtonProps & { + ws: AstalHyprland.Workspace; +}; + +const Workspace = ({ ws, ...props }: WsButtonProps) => { + const hyprland = AstalHyprland.get_default(); + const classNames = Variable.derive( + [bind(hyprland, "focusedWorkspace"), bind(hyprland, "clients")], + (fws, _) => { + const classes = ["workspace-button"]; + + const active = fws.id == ws.id; + active && classes.push("active"); + + const occupied = hyprland.get_workspace(ws.id)?.get_clients().length > 0; + occupied && classes.push("occupied"); + return classes; + }, + ); + + return ( +