Compare commits

...

2 Commits

Author SHA1 Message Date
06325b09b8 [Hyprland] Update readmes 2025-10-17 10:44:33 +02:00
539ec34b4c [Astal] Finish modes, move tray into quickactions menu 2025-10-17 10:44:33 +02:00
9 changed files with 192 additions and 125 deletions

View File

@@ -9,10 +9,10 @@ For my neovim config, see [here](https://git.janishutz.com/janishutz/nvim)
![screenshot of desktop with 2 screens](./assets/screenshot.png) ![screenshot of desktop with 2 screens](./assets/screenshot.png)
I am currently working on redoing my keybinds for Hyprland, in what I call `hyprmode`: I am currently working on redoing my keybinds for Hyprland, in what I call `hyprvim`:
They are going to use submaps and will be significantly different, yet still familiar. They are going to use submaps and will be significantly different, yet still familiar.
This will enable me to have many more keybinds with reasonable starter bindings. This will enable me to have many more keybinds with reasonable starter bindings.
I will also update Astal to have a mode indicator if `hyprmode` is enabled. I will also update Astal to have a mode indicator if `hyprvim` is enabled.
## Features ## Features
- Astal4 based Status Bar and Quick Actions menu - Astal4 based Status Bar and Quick Actions menu

View File

@@ -1,44 +1,58 @@
import { Gtk } from "astal/gtk4"; import Audio from './modules/Audio/Audio';
import Power from "./modules/Power"; import {
import Audio from "./modules/Audio/Audio"; BatteryBox
import Bluetooth from "./modules/Bluetooth/Bluetooth"; } from './modules/Battery';
import Brightness from "./modules/Brightness/Brightness"; import Bluetooth from './modules/Bluetooth/Bluetooth';
import Player from "./modules/Player/Player"; import Brightness from './modules/Brightness/Brightness';
import { BatteryBox } from "./modules/Battery"; import {
import { exec } from "astal"; Gtk
import Network from "./modules/Networking/Network"; } from 'astal/gtk4';
import Network from './modules/Networking/Network';
import Player from './modules/Player/Player';
import Power from './modules/Power';
import SysTray from './modules/SysTray';
import {
exec
} from 'astal';
const QuickActions = () => { const QuickActions = () => {
const popover = new Gtk.Popover({ cssClasses: ["quick-actions-wrapper"] }); const popover = new Gtk.Popover( {
popover.set_child(renderQuickActions()); 'cssClasses': [ 'quick-actions-wrapper' ]
} );
popover.set_child( renderQuickActions() );
return popover; return popover;
}; };
const renderQuickActions = () => { const renderQuickActions = () => {
const user = exec("/bin/sh -c whoami"); const user = exec( '/bin/sh -c whoami' );
const profile = exec("/bin/fish -c get-profile-picture"); const profile = exec( '/bin/fish -c get-profile-picture' );
const cwd = exec("pwd"); const cwd = exec( 'pwd' );
const um = Power.UserMenu(); const um = Power.UserMenu();
return ( return (
<box visible cssClasses={["quick-actions", "popover-box"]} vertical> <box visible cssClasses={[
'quick-actions',
'popover-box'
]} vertical>
<centerbox <centerbox
startWidget={ startWidget={
<button <button
onClicked={() => um.popup()} onClicked={() => um.popup()}
cssClasses={["stealthy-button"]} cssClasses={[ 'stealthy-button' ]}
child={ child={
<box> <box>
{um} {um}
<Gtk.Frame <Gtk.Frame
cssClasses={["avatar-icon"]} cssClasses={[ 'avatar-icon' ]}
child={ child={
<image <image
file={ file={
profile !== "" profile !== ''
? profile ? profile
: cwd + : cwd
"/no-avatar-icon.jpg" + '/no-avatar-icon.jpg'
} }
></image> ></image>
} }
@@ -53,6 +67,7 @@ const renderQuickActions = () => {
hexpand={false} hexpand={false}
> >
<BatteryBox></BatteryBox> <BatteryBox></BatteryBox>
<SysTray.SystemTray></SysTray.SystemTray>
<Power.Power></Power.Power> <Power.Power></Power.Power>
</box> </box>
} }

View File

@@ -1,48 +1,56 @@
import { exec } from "astal"; import {
import { Gtk } from "astal/gtk4"; exec
} from 'astal';
import {
Gtk
} from 'astal/gtk4';
const PowerMenu = (): Gtk.Popover => { const PowerMenu = (): Gtk.Popover => {
const popover = new Gtk.Popover({ cssClasses: ["PowerMenu"] }); const popover = new Gtk.Popover( {
'cssClasses': [ 'PowerMenu' ]
} );
const powerMenuBox = () => { const powerMenuBox = () => {
return ( return (
<box> <box>
<button <button
cssClasses={["power-button"]} cssClasses={[ 'power-button' ]}
child={ child={
<image iconName={"system-shutdown-symbolic"}></image> <image iconName={'system-shutdown-symbolic'}></image>
} }
onClicked={() => exec("/bin/sh -c 'shutdown now'")} onClicked={() => exec( '/bin/sh -c \'shutdown now\'' )}
></button> ></button>
<button <button
cssClasses={["power-button"]} cssClasses={[ 'power-button' ]}
child={<image iconName={"system-reboot-symbolic"}></image>} child={<image iconName={'system-reboot-symbolic'}></image>}
onClicked={() => exec("/bin/sh -c 'reboot'")} onClicked={() => exec( '/bin/sh -c \'reboot\'' )}
></button> ></button>
<button <button
cssClasses={["power-button"]} cssClasses={[ 'power-button' ]}
child={<image iconName={"system-suspend-symbolic"}></image>} child={<image iconName={'system-suspend-symbolic'}></image>}
onClicked={() => exec("/bin/sh -c 'systemctl suspend'")} onClicked={() => exec( '/bin/sh -c \'systemctl suspend\'' )}
></button> ></button>
</box> </box>
); );
}; };
popover.set_child(powerMenuBox()); popover.set_child( powerMenuBox() );
return popover; return popover;
}; };
const Power = () => { const Power = () => {
const pm = PowerMenu(); const pm = PowerMenu();
return ( return (
<button <button
widthRequest={0} widthRequest={0}
hexpand={false} hexpand={false}
vexpand={false} vexpand={false}
cssClasses={["power-menu-button"]} cssClasses={[ 'power-menu-button' ]}
child={ child={
<box> <box>
<image iconName={"system-shutdown-symbolic"}></image> <image iconName={'system-shutdown-symbolic'}></image>
{pm} {pm}
</box> </box>
} }
@@ -58,24 +66,24 @@ const UserMenu = (): Gtk.Popover => {
return ( return (
<box> <box>
<button <button
cssClasses={["power-button"]} cssClasses={[ 'power-button' ]}
child={ child={
<image iconName={"system-lock-screen-symbolic"}></image> <image iconName={'system-lock-screen-symbolic'}></image>
} }
onClicked={() => exec("/bin/sh -c 'hyprlock'")} onClicked={() => exec( '/bin/sh -c \'hyprlock\'' )}
></button> ></button>
<button <button
cssClasses={["power-button"]} cssClasses={[ 'power-button' ]}
child={<image iconName={"system-log-out-symbolic"}></image>} child={<image iconName={'system-log-out-symbolic'}></image>}
onClicked={() => onClicked={() => exec( '/bin/sh -c \'hyprctl dispatch exit 0\'' )
exec("/bin/sh -c 'hyprctl dispatch exit 0'")
} }
></button> ></button>
</box> </box>
); );
}; };
popover.set_child(powerMenuBox()); popover.set_child( powerMenuBox() );
return popover; return popover;
}; };

View File

@@ -0,0 +1,88 @@
import AstalTray from 'gi://AstalTray';
import {
GObject
} from 'astal';
import {
Gtk
} from 'astal/gtk4';
const SYNC = GObject.BindingFlags.SYNC_CREATE;
const SysTray = () => {
const trayBox = new Gtk.Box( {
'cssClasses': [ '' ]
} );
const tray = AstalTray.get_default();
const trayItems = new Map<string, Gtk.MenuButton>();
const trayAddedHandler = tray.connect( 'item-added', ( _, id ) => {
const item = tray.get_item( id );
const popover = Gtk.PopoverMenu.new_from_model( item.menu_model );
const icon = new Gtk.Image();
const button = new Gtk.MenuButton( {
popover,
'child': icon,
'cssClasses': [ 'tray-item' ],
} );
item.bind_property(
'gicon', icon, 'gicon', SYNC
);
popover.insert_action_group( 'dbusmenu', item.action_group );
item.connect( 'notify::action-group', () => {
popover.insert_action_group( 'dbusmenu', item.action_group );
} );
trayItems.set( id, button );
trayBox.append( button );
} );
const trayRemovedHandler = tray.connect( 'item-removed', ( _, id ) => {
const button = trayItems.get( id );
if ( button ) {
trayBox.remove( button );
button.run_dispose();
trayItems.delete( id );
}
} );
trayBox.connect( 'destroy', () => {
tray.disconnect( trayAddedHandler );
tray.disconnect( trayRemovedHandler );
} );
return trayBox;
};
const TrayPopover = () => {
const popover = new Gtk.Popover( {
'cssClasses': [ 'TrayPopover' ]
} );
popover.set_child( SysTray() );
return popover;
};
const SystemTray = () => {
const systray = TrayPopover();
return (
<button
widthRequest={0}
hexpand={false}
vexpand={false}
cssClasses={[ 'power-menu-button' ]}
child={
<box>
<image iconName={'systemtray'}></image>
{systray}
</box>
}
onClicked={() => systray.popup()}
/>
);
};
export default {
SystemTray
};

View File

@@ -39,7 +39,6 @@ const Bar = ( {
> >
<Hyprland.ModeStatus /> <Hyprland.ModeStatus />
<Calendar.Time /> <Calendar.Time />
<SystemInfo.SystemInfo />
<Hyprland.Workspace /> <Hyprland.Workspace />
</box> </box>
} }
@@ -50,7 +49,7 @@ const Bar = ( {
halign={Gtk.Align.END} halign={Gtk.Align.END}
cssClasses={[ 'BarRight' ]} cssClasses={[ 'BarRight' ]}
> >
<Hyprland.SysTray /> <SystemInfo.SystemInfo />
<QuickView.QuickView /> <QuickView.QuickView />
</box> </box>
} }

View File

@@ -20,6 +20,11 @@ window.Bar {
border-radius: 20px; border-radius: 20px;
font-family: $monospace-font; font-family: $monospace-font;
&.command-mode {
background-color: darkslategray;
color: white;
}
&.windowing-mode { &.windowing-mode {
background-color: darkslategray; background-color: darkslategray;
color: white; color: white;

View File

@@ -1,62 +1,15 @@
import { import {
GObject, bind, bind,
exec, exec,
readFile readFile
} from 'astal'; } from 'astal';
import AstalHyprland from 'gi://AstalHyprland'; import AstalHyprland from 'gi://AstalHyprland';
import AstalTray from 'gi://AstalTray';
import { import {
Gtk Gtk
} from 'astal/gtk4'; } from 'astal/gtk4';
import Pango from 'gi://Pango?version=1.0'; import Pango from 'gi://Pango?version=1.0';
const hypr = AstalHyprland.get_default(); const hypr = AstalHyprland.get_default();
const SYNC = GObject.BindingFlags.SYNC_CREATE;
const SysTray = () => {
const trayBox = new Gtk.Box( {
'cssClasses': [ 'bar-button' ]
} );
const tray = AstalTray.get_default();
const trayItems = new Map<string, Gtk.MenuButton>();
const trayAddedHandler = tray.connect( 'item-added', ( _, id ) => {
const item = tray.get_item( id );
const popover = Gtk.PopoverMenu.new_from_model( item.menu_model );
const icon = new Gtk.Image();
const button = new Gtk.MenuButton( {
popover,
'child': icon,
'cssClasses': [ 'tray-item' ],
} );
item.bind_property(
'gicon', icon, 'gicon', SYNC
);
popover.insert_action_group( 'dbusmenu', item.action_group );
item.connect( 'notify::action-group', () => {
popover.insert_action_group( 'dbusmenu', item.action_group );
} );
trayItems.set( id, button );
trayBox.append( button );
} );
const trayRemovedHandler = tray.connect( 'item-removed', ( _, id ) => {
const button = trayItems.get( id );
if ( button ) {
trayBox.remove( button );
button.run_dispose();
trayItems.delete( id );
}
} );
trayBox.connect( 'destroy', () => {
tray.disconnect( trayAddedHandler );
tray.disconnect( trayRemovedHandler );
} );
return trayBox;
};
const Workspace = () => { const Workspace = () => {
return ( return (
@@ -123,33 +76,33 @@ const ActiveWindow = () => {
type submaps = 'device' | 'launch' | 'workspace' | 'windowing' | 'screenshotting' | 'notifications' | ''; type submaps = 'device' | 'launch' | 'workspace' | 'windowing' | 'screenshotting' | 'notifications' | '';
const ModeStatus = () => { const ModeStatus = () => {
let isUsingHyprmode = false; let isUsingHyprvim = false;
try { try {
const path = exec( 'bash -c "cd ~ && pwd"' ) + '/.config/hyprmode'; const path = exec( 'bash -c "cd ~ && pwd"' ) + '/.config/hyprvim';
isUsingHyprmode = readFile( path ).trim() === 'y'; isUsingHyprvim = readFile( path ).trim() === 'y';
} catch ( e ) { } catch ( e ) {
printerr( 'Failed to read hyprmode config', e ); printerr( 'Failed to read hyprvim config', e );
} }
const label = new Gtk.Label(); const label = new Gtk.Label();
if ( !isUsingHyprmode ) return label; if ( !isUsingHyprvim ) return label;
print( '==> Using hyprmode config' ); print( '==> Using hyprvim config' );
const map = { const map = {
'device': 'D', 'device': 'DEV',
'launch': 'L', 'launch': 'LNC',
'workspace': 'W', 'workspace': 'WSP',
'windowing': 'M', 'windowing': 'WIN',
'screenshotting': 'S', 'screenshotting': 'SCS',
'notifications': 'N', 'notifications': 'NOT',
'': 'N' '': 'NRM'
}; };
label.label = 'N'; label.label = map[''];
label.cssClasses = [ 'mode-status' ]; label.cssClasses = [ 'mode-status' ];
// TODO: Possibly add popover to it that lists binds // TODO: Possibly add popover to it that lists binds
@@ -189,6 +142,5 @@ const WindowPopoverBox = () => {
export default { export default {
Workspace, Workspace,
ActiveWindow, ActiveWindow,
SysTray,
ModeStatus ModeStatus
}; };

View File

@@ -1,4 +1,4 @@
# Hyprmode # hyprvim
Using Vim Motions and in general vim-style commands is really neat - so why not apply to the Window Manager as well? Using Vim Motions and in general vim-style commands is really neat - so why not apply to the Window Manager as well?
## Mapping ## Mapping

28
setup
View File

@@ -55,19 +55,19 @@ else
fi fi
# ──────────────────────────────────────────────────────────────────── # ────────────────────────────────────────────────────────────────────
# Hyprmode config # hyprvim config
hyprmode="" hyprvim=""
if [[ -f ~/.config/hyprmode ]]; then if [[ -f ~/.config/hyprvim ]]; then
echo "Hyprmode config already specified, skipping" echo "hyprvim config already specified, skipping"
hyprmode=$(cat ~/.config/hyprmode) hyprvim=$(cat ~/.config/hyprvim)
else else
read -p "Would you like to use Hyprmode? (Y/n) " hyprmode read -p "Would you like to use hyprvim? (Y/n) " hyprvim
fi fi
hyprmode=$(echo "$hyprmode" | tr '[:upper:]' '[:lower:]') hyprvim=$(echo "$hyprvim" | tr '[:upper:]' '[:lower:]')
if [[ "$hyprmode" == "" ]]; then if [[ "$hyprvim" == "" ]]; then
hyprmode="y" hyprvim="y"
fi fi
echo "$hyprmode" >~/.config/hyprmode echo "$hyprvim" >~/.config/hyprvim
# ──────────────────────────────────────────────────────────────────── # ────────────────────────────────────────────────────────────────────
echo "=> Moving configs to correct destinations" echo "=> Moving configs to correct destinations"
@@ -88,12 +88,12 @@ else
cp -f ~/.config/hypr/hyprland_laptop.conf ~/.config/hypr/hyprland.conf cp -f ~/.config/hypr/hyprland_laptop.conf ~/.config/hypr/hyprland.conf
fi fi
# Enable or disable "Hyprmode" (using hyprland with vim-inspired modes) # Enable or disable "hyprvim" (using hyprland with vim-inspired modes)
if [[ "$hyprmode" == "y" ]]; then if [[ "$hyprvim" == "y" ]]; then
echo "Enabling hyprmode" echo "Enabling hyprvim"
mv -f ~/.config/hypr/hyprland/mode-binds.conf ~/.config/hypr/hyprland/binds.conf mv -f ~/.config/hypr/hyprland/mode-binds.conf ~/.config/hypr/hyprland/binds.conf
else else
echo "Disabling hyprmode" echo "Disabling hyprvim"
rm -rf ~/.config/hypr/hyprland/modal-binds rm -rf ~/.config/hypr/hyprland/modal-binds
rm ~/.config/hypr/hyprland/mode-binds.conf rm ~/.config/hypr/hyprland/mode-binds.conf
fi fi