feat: add XDG autostart support for automatic application launching
feat: implement demo mode for automated feature showcase feat: add XEmbed system tray protocol for external application icons feat: add window snapping to left and right halves feat: add minimize/restore toggle with Alt+F9 keybinding docs: update README with new features and configuration details build: update build system for new autostart and demo modules
This commit is contained in:
parent
c76a620012
commit
d0b1669cb4
43
README.md
43
README.md
@ -29,6 +29,7 @@ DWN is a **window manager** - the program that controls how windows look and beh
|
|||||||
- 3 window layouts: Tiling (windows don't overlap), Floating (like normal), Monocle (fullscreen)
|
- 3 window layouts: Tiling (windows don't overlap), Floating (like normal), Monocle (fullscreen)
|
||||||
- 9 virtual workspaces to organize your windows
|
- 9 virtual workspaces to organize your windows
|
||||||
- Built-in panels with clock, taskbar, and system tray
|
- Built-in panels with clock, taskbar, and system tray
|
||||||
|
- XDG Autostart support (automatically starts nm-applet, blueman, etc.)
|
||||||
- WiFi network selector with dropdown menu (like XFCE)
|
- WiFi network selector with dropdown menu (like XFCE)
|
||||||
- Audio volume indicator with scroll-to-adjust
|
- Audio volume indicator with scroll-to-adjust
|
||||||
- Desktop notifications
|
- Desktop notifications
|
||||||
@ -102,6 +103,7 @@ sudo make install
|
|||||||
| `Super + Backspace` | **Exit DWN** (important!) |
|
| `Super + Backspace` | **Exit DWN** (important!) |
|
||||||
| `Super + S` | **Show all shortcuts** |
|
| `Super + S` | **Show all shortcuts** |
|
||||||
| `Super + T` | **Start interactive tutorial** |
|
| `Super + T` | **Start interactive tutorial** |
|
||||||
|
| `Super + Shift + D` | **Demo mode** (automated feature showcase) |
|
||||||
|
|
||||||
### Workspaces
|
### Workspaces
|
||||||
|
|
||||||
@ -116,9 +118,10 @@ sudo make install
|
|||||||
|
|
||||||
| Keys | What it does |
|
| Keys | What it does |
|
||||||
|------|--------------|
|
|------|--------------|
|
||||||
| `Super + F9` | Toggle floating mode |
|
| `Alt + F9` | Toggle minimize/restore |
|
||||||
| `Alt + F10` | Toggle maximize |
|
| `Alt + F10` | Toggle maximize |
|
||||||
| `Alt + F11` | Toggle fullscreen |
|
| `Alt + F11` | Toggle fullscreen |
|
||||||
|
| `Super + F9` | Toggle floating mode |
|
||||||
| `Alt + Tab` | Cycle windows forward |
|
| `Alt + Tab` | Cycle windows forward |
|
||||||
| `Alt + Shift + Tab` | Cycle windows backward |
|
| `Alt + Shift + Tab` | Cycle windows backward |
|
||||||
|
|
||||||
@ -131,6 +134,8 @@ sudo make install
|
|||||||
| `Super + L` | Expand main area |
|
| `Super + L` | Expand main area |
|
||||||
| `Super + I` | Add window to main area |
|
| `Super + I` | Add window to main area |
|
||||||
| `Super + D` | Remove window from main area |
|
| `Super + D` | Remove window from main area |
|
||||||
|
| `Super + Left` | Snap window to left half (50% width) |
|
||||||
|
| `Super + Right` | Snap window to right half (50% width) |
|
||||||
|
|
||||||
### AI Features (Optional)
|
### AI Features (Optional)
|
||||||
|
|
||||||
@ -160,7 +165,22 @@ The bottom panel displays a scrolling news ticker. Navigate articles with these
|
|||||||
|
|
||||||
## System Tray
|
## System Tray
|
||||||
|
|
||||||
The top panel includes a system tray on the right side with battery, audio, and WiFi indicators.
|
The top panel includes a system tray on the right side with support for external application icons plus built-in battery, audio, and WiFi indicators.
|
||||||
|
|
||||||
|
### XEmbed System Tray (External Application Icons)
|
||||||
|
|
||||||
|
DWN implements the freedesktop.org XEmbed System Tray protocol, allowing external applications to dock their status icons in the panel - just like XFCE, GNOME, or KDE. This means applications like:
|
||||||
|
|
||||||
|
- **Telegram** - Shows notification icon when messages arrive
|
||||||
|
- **nm-applet** - NetworkManager GUI applet
|
||||||
|
- **blueman-applet** - Bluetooth manager
|
||||||
|
- **pasystray** - PulseAudio volume control
|
||||||
|
- **udiskie** - USB device automounter
|
||||||
|
- **clipit/parcellite** - Clipboard managers
|
||||||
|
|
||||||
|
...will automatically appear in your system tray when launched.
|
||||||
|
|
||||||
|
Simply start any application with tray icon support and it will dock itself in the panel. Click on icons to interact with them - clicks are forwarded to the application.
|
||||||
|
|
||||||
### Battery Indicator
|
### Battery Indicator
|
||||||
|
|
||||||
@ -243,6 +263,25 @@ gap = 10
|
|||||||
border_width = 2
|
border_width = 2
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Configure autostart:**
|
||||||
|
```ini
|
||||||
|
[autostart]
|
||||||
|
enabled = true
|
||||||
|
xdg_autostart = true
|
||||||
|
path = ~/.config/dwn/autostart.d
|
||||||
|
```
|
||||||
|
|
||||||
|
DWN automatically starts applications from XDG autostart directories:
|
||||||
|
- `/etc/xdg/autostart/` - System apps (nm-applet, blueman, etc.)
|
||||||
|
- `~/.config/autostart/` - User apps
|
||||||
|
- `~/.config/dwn/autostart.d/` - DWN-specific symlinks
|
||||||
|
|
||||||
|
To add custom autostart apps:
|
||||||
|
```bash
|
||||||
|
mkdir -p ~/.config/dwn/autostart.d
|
||||||
|
ln -s /usr/bin/telegram-desktop ~/.config/dwn/autostart.d/
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## AI Features (Optional)
|
## AI Features (Optional)
|
||||||
|
|||||||
BIN
build/ai.o
BIN
build/ai.o
Binary file not shown.
BIN
build/atoms.o
BIN
build/atoms.o
Binary file not shown.
7
build/autostart.d
Normal file
7
build/autostart.d
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
build/autostart.o: src/autostart.c include/autostart.h include/dwn.h \
|
||||||
|
include/config.h include/dwn.h include/util.h
|
||||||
|
include/autostart.h:
|
||||||
|
include/dwn.h:
|
||||||
|
include/config.h:
|
||||||
|
include/dwn.h:
|
||||||
|
include/util.h:
|
||||||
BIN
build/autostart.o
Normal file
BIN
build/autostart.o
Normal file
Binary file not shown.
BIN
build/client.o
BIN
build/client.o
Binary file not shown.
BIN
build/config.o
BIN
build/config.o
Binary file not shown.
Binary file not shown.
52
build/demo.d
Normal file
52
build/demo.d
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
build/demo.o: src/demo.c include/demo.h include/dwn.h \
|
||||||
|
include/notifications.h include/dwn.h /usr/include/dbus-1.0/dbus/dbus.h \
|
||||||
|
/usr/lib/x86_64-linux-gnu/dbus-1.0/include/dbus/dbus-arch-deps.h \
|
||||||
|
/usr/include/dbus-1.0/dbus/dbus-macros.h \
|
||||||
|
/usr/include/dbus-1.0/dbus/dbus-address.h \
|
||||||
|
/usr/include/dbus-1.0/dbus/dbus-types.h \
|
||||||
|
/usr/include/dbus-1.0/dbus/dbus-errors.h \
|
||||||
|
/usr/include/dbus-1.0/dbus/dbus-protocol.h \
|
||||||
|
/usr/include/dbus-1.0/dbus/dbus-bus.h \
|
||||||
|
/usr/include/dbus-1.0/dbus/dbus-connection.h \
|
||||||
|
/usr/include/dbus-1.0/dbus/dbus-memory.h \
|
||||||
|
/usr/include/dbus-1.0/dbus/dbus-message.h \
|
||||||
|
/usr/include/dbus-1.0/dbus/dbus-shared.h \
|
||||||
|
/usr/include/dbus-1.0/dbus/dbus-misc.h \
|
||||||
|
/usr/include/dbus-1.0/dbus/dbus-pending-call.h \
|
||||||
|
/usr/include/dbus-1.0/dbus/dbus-server.h \
|
||||||
|
/usr/include/dbus-1.0/dbus/dbus-signature.h \
|
||||||
|
/usr/include/dbus-1.0/dbus/dbus-syntax.h \
|
||||||
|
/usr/include/dbus-1.0/dbus/dbus-threads.h include/workspace.h \
|
||||||
|
include/client.h include/layout.h include/keys.h include/ai.h \
|
||||||
|
include/news.h include/util.h include/panel.h include/config.h
|
||||||
|
include/demo.h:
|
||||||
|
include/dwn.h:
|
||||||
|
include/notifications.h:
|
||||||
|
include/dwn.h:
|
||||||
|
/usr/include/dbus-1.0/dbus/dbus.h:
|
||||||
|
/usr/lib/x86_64-linux-gnu/dbus-1.0/include/dbus/dbus-arch-deps.h:
|
||||||
|
/usr/include/dbus-1.0/dbus/dbus-macros.h:
|
||||||
|
/usr/include/dbus-1.0/dbus/dbus-address.h:
|
||||||
|
/usr/include/dbus-1.0/dbus/dbus-types.h:
|
||||||
|
/usr/include/dbus-1.0/dbus/dbus-errors.h:
|
||||||
|
/usr/include/dbus-1.0/dbus/dbus-protocol.h:
|
||||||
|
/usr/include/dbus-1.0/dbus/dbus-bus.h:
|
||||||
|
/usr/include/dbus-1.0/dbus/dbus-connection.h:
|
||||||
|
/usr/include/dbus-1.0/dbus/dbus-memory.h:
|
||||||
|
/usr/include/dbus-1.0/dbus/dbus-message.h:
|
||||||
|
/usr/include/dbus-1.0/dbus/dbus-shared.h:
|
||||||
|
/usr/include/dbus-1.0/dbus/dbus-misc.h:
|
||||||
|
/usr/include/dbus-1.0/dbus/dbus-pending-call.h:
|
||||||
|
/usr/include/dbus-1.0/dbus/dbus-server.h:
|
||||||
|
/usr/include/dbus-1.0/dbus/dbus-signature.h:
|
||||||
|
/usr/include/dbus-1.0/dbus/dbus-syntax.h:
|
||||||
|
/usr/include/dbus-1.0/dbus/dbus-threads.h:
|
||||||
|
include/workspace.h:
|
||||||
|
include/client.h:
|
||||||
|
include/layout.h:
|
||||||
|
include/keys.h:
|
||||||
|
include/ai.h:
|
||||||
|
include/news.h:
|
||||||
|
include/util.h:
|
||||||
|
include/panel.h:
|
||||||
|
include/config.h:
|
||||||
BIN
build/demo.o
Normal file
BIN
build/demo.o
Normal file
Binary file not shown.
@ -18,7 +18,7 @@ build/keys.o: src/keys.c include/keys.h include/dwn.h include/client.h \
|
|||||||
/usr/include/dbus-1.0/dbus/dbus-signature.h \
|
/usr/include/dbus-1.0/dbus/dbus-signature.h \
|
||||||
/usr/include/dbus-1.0/dbus/dbus-syntax.h \
|
/usr/include/dbus-1.0/dbus/dbus-syntax.h \
|
||||||
/usr/include/dbus-1.0/dbus/dbus-threads.h include/news.h \
|
/usr/include/dbus-1.0/dbus/dbus-threads.h include/news.h \
|
||||||
include/applauncher.h
|
include/applauncher.h include/decorations.h include/demo.h
|
||||||
include/keys.h:
|
include/keys.h:
|
||||||
include/dwn.h:
|
include/dwn.h:
|
||||||
include/client.h:
|
include/client.h:
|
||||||
@ -47,3 +47,5 @@ include/notifications.h:
|
|||||||
/usr/include/dbus-1.0/dbus/dbus-threads.h:
|
/usr/include/dbus-1.0/dbus/dbus-threads.h:
|
||||||
include/news.h:
|
include/news.h:
|
||||||
include/applauncher.h:
|
include/applauncher.h:
|
||||||
|
include/decorations.h:
|
||||||
|
include/demo.h:
|
||||||
|
|||||||
BIN
build/keys.o
BIN
build/keys.o
Binary file not shown.
@ -19,7 +19,8 @@ build/main.o: src/main.c include/dwn.h include/config.h include/dwn.h \
|
|||||||
/usr/include/dbus-1.0/dbus/dbus-signature.h \
|
/usr/include/dbus-1.0/dbus/dbus-signature.h \
|
||||||
/usr/include/dbus-1.0/dbus/dbus-syntax.h \
|
/usr/include/dbus-1.0/dbus/dbus-syntax.h \
|
||||||
/usr/include/dbus-1.0/dbus/dbus-threads.h include/systray.h \
|
/usr/include/dbus-1.0/dbus/dbus-threads.h include/systray.h \
|
||||||
include/news.h include/applauncher.h include/ai.h include/util.h
|
include/news.h include/applauncher.h include/ai.h include/autostart.h \
|
||||||
|
include/demo.h include/util.h
|
||||||
include/dwn.h:
|
include/dwn.h:
|
||||||
include/config.h:
|
include/config.h:
|
||||||
include/dwn.h:
|
include/dwn.h:
|
||||||
@ -53,4 +54,6 @@ include/systray.h:
|
|||||||
include/news.h:
|
include/news.h:
|
||||||
include/applauncher.h:
|
include/applauncher.h:
|
||||||
include/ai.h:
|
include/ai.h:
|
||||||
|
include/autostart.h:
|
||||||
|
include/demo.h:
|
||||||
include/util.h:
|
include/util.h:
|
||||||
|
|||||||
BIN
build/main.o
BIN
build/main.o
Binary file not shown.
BIN
build/news.o
BIN
build/news.o
Binary file not shown.
Binary file not shown.
BIN
build/panel.o
BIN
build/panel.o
Binary file not shown.
@ -17,7 +17,7 @@ build/systray.o: src/systray.c include/systray.h include/dwn.h \
|
|||||||
/usr/include/dbus-1.0/dbus/dbus-server.h \
|
/usr/include/dbus-1.0/dbus/dbus-server.h \
|
||||||
/usr/include/dbus-1.0/dbus/dbus-signature.h \
|
/usr/include/dbus-1.0/dbus/dbus-signature.h \
|
||||||
/usr/include/dbus-1.0/dbus/dbus-syntax.h \
|
/usr/include/dbus-1.0/dbus/dbus-syntax.h \
|
||||||
/usr/include/dbus-1.0/dbus/dbus-threads.h
|
/usr/include/dbus-1.0/dbus/dbus-threads.h include/atoms.h
|
||||||
include/systray.h:
|
include/systray.h:
|
||||||
include/dwn.h:
|
include/dwn.h:
|
||||||
include/panel.h:
|
include/panel.h:
|
||||||
@ -42,3 +42,4 @@ include/notifications.h:
|
|||||||
/usr/include/dbus-1.0/dbus/dbus-signature.h:
|
/usr/include/dbus-1.0/dbus/dbus-signature.h:
|
||||||
/usr/include/dbus-1.0/dbus/dbus-syntax.h:
|
/usr/include/dbus-1.0/dbus/dbus-syntax.h:
|
||||||
/usr/include/dbus-1.0/dbus/dbus-threads.h:
|
/usr/include/dbus-1.0/dbus/dbus-threads.h:
|
||||||
|
include/atoms.h:
|
||||||
|
|||||||
BIN
build/systray.o
BIN
build/systray.o
Binary file not shown.
@ -1,6 +1,6 @@
|
|||||||
build/workspace.o: src/workspace.c include/workspace.h include/dwn.h \
|
build/workspace.o: src/workspace.c include/workspace.h include/dwn.h \
|
||||||
include/client.h include/layout.h include/atoms.h include/util.h \
|
include/client.h include/layout.h include/atoms.h include/util.h \
|
||||||
include/config.h
|
include/config.h include/panel.h
|
||||||
include/workspace.h:
|
include/workspace.h:
|
||||||
include/dwn.h:
|
include/dwn.h:
|
||||||
include/client.h:
|
include/client.h:
|
||||||
@ -8,3 +8,4 @@ include/layout.h:
|
|||||||
include/atoms.h:
|
include/atoms.h:
|
||||||
include/util.h:
|
include/util.h:
|
||||||
include/config.h:
|
include/config.h:
|
||||||
|
include/panel.h:
|
||||||
|
|||||||
Binary file not shown.
@ -91,3 +91,16 @@ model = google/gemini-2.0-flash-exp:free
|
|||||||
# Can also be set via EXA_API_KEY environment variable
|
# Can also be set via EXA_API_KEY environment variable
|
||||||
# Sign up and get your key at: https://dashboard.exa.ai/api-keys
|
# Sign up and get your key at: https://dashboard.exa.ai/api-keys
|
||||||
# exa_api_key = your-exa-key-here
|
# exa_api_key = your-exa-key-here
|
||||||
|
|
||||||
|
[autostart]
|
||||||
|
# Enable XDG autostart support (default: true)
|
||||||
|
# Automatically starts applications from XDG autostart directories
|
||||||
|
enabled = true
|
||||||
|
|
||||||
|
# Scan XDG .desktop files (default: true)
|
||||||
|
# Directories: /etc/xdg/autostart and ~/.config/autostart
|
||||||
|
xdg_autostart = true
|
||||||
|
|
||||||
|
# Additional directory for symlinks and scripts (default: ~/.config/dwn/autostart.d)
|
||||||
|
# Create symlinks to binaries you want to start: ln -s /usr/bin/telegram-desktop ~/.config/dwn/autostart.d/
|
||||||
|
path = ~/.config/dwn/autostart.d
|
||||||
|
|||||||
@ -76,6 +76,8 @@ typedef struct {
|
|||||||
|
|
||||||
Atom NET_SYSTEM_TRAY_OPCODE;
|
Atom NET_SYSTEM_TRAY_OPCODE;
|
||||||
Atom NET_SYSTEM_TRAY_S0;
|
Atom NET_SYSTEM_TRAY_S0;
|
||||||
|
Atom NET_SYSTEM_TRAY_ORIENTATION;
|
||||||
|
Atom NET_SYSTEM_TRAY_VISUAL;
|
||||||
Atom MANAGER;
|
Atom MANAGER;
|
||||||
Atom XEMBED;
|
Atom XEMBED;
|
||||||
Atom XEMBED_INFO;
|
Atom XEMBED_INFO;
|
||||||
@ -130,4 +132,6 @@ void atoms_send_protocol(Window window, Atom protocol, Time timestamp);
|
|||||||
void atoms_send_client_message(Window window, Atom message_type,
|
void atoms_send_client_message(Window window, Atom message_type,
|
||||||
long data0, long data1, long data2, long data3, long data4);
|
long data0, long data1, long data2, long data3, long data4);
|
||||||
|
|
||||||
|
bool atoms_update_wm_state(Window window, Atom state, bool add);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
14
include/autostart.h
Normal file
14
include/autostart.h
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* DWN - Desktop Window Manager
|
||||||
|
* retoor <retoor@molodetz.nl>
|
||||||
|
* XDG Autostart support
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef DWN_AUTOSTART_H
|
||||||
|
#define DWN_AUTOSTART_H
|
||||||
|
|
||||||
|
void autostart_init(void);
|
||||||
|
void autostart_run(void);
|
||||||
|
void autostart_cleanup(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -54,6 +54,10 @@ struct Config {
|
|||||||
|
|
||||||
char config_path[512];
|
char config_path[512];
|
||||||
char log_path[512];
|
char log_path[512];
|
||||||
|
|
||||||
|
bool autostart_enabled;
|
||||||
|
bool autostart_xdg;
|
||||||
|
char autostart_path[512];
|
||||||
};
|
};
|
||||||
|
|
||||||
Config *config_create(void);
|
Config *config_create(void);
|
||||||
|
|||||||
36
include/demo.h
Normal file
36
include/demo.h
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* DWN - Desktop Window Manager
|
||||||
|
* retoor <retoor@molodetz.nl>
|
||||||
|
* Demo mode - automated feature showcase
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef DWN_DEMO_H
|
||||||
|
#define DWN_DEMO_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
DEMO_IDLE,
|
||||||
|
DEMO_INTRO,
|
||||||
|
DEMO_WINDOW_MGMT,
|
||||||
|
DEMO_WORKSPACES,
|
||||||
|
DEMO_LAYOUTS,
|
||||||
|
DEMO_SNAPPING,
|
||||||
|
DEMO_PANELS,
|
||||||
|
DEMO_AI,
|
||||||
|
DEMO_NEWS,
|
||||||
|
DEMO_SHORTCUTS,
|
||||||
|
DEMO_COMPLETE
|
||||||
|
} DemoPhase;
|
||||||
|
|
||||||
|
void demo_init(void);
|
||||||
|
void demo_cleanup(void);
|
||||||
|
void demo_start(void);
|
||||||
|
void demo_stop(void);
|
||||||
|
void demo_update(void);
|
||||||
|
bool demo_is_active(void);
|
||||||
|
DemoPhase demo_get_phase(void);
|
||||||
|
int demo_get_step(void);
|
||||||
|
const char *demo_get_phase_name(DemoPhase phase);
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -50,6 +50,7 @@ void key_cycle_layout(void);
|
|||||||
void key_toggle_floating(void);
|
void key_toggle_floating(void);
|
||||||
void key_toggle_fullscreen(void);
|
void key_toggle_fullscreen(void);
|
||||||
void key_toggle_maximize(void);
|
void key_toggle_maximize(void);
|
||||||
|
void key_toggle_minimize(void);
|
||||||
void key_focus_next(void);
|
void key_focus_next(void);
|
||||||
void key_focus_prev(void);
|
void key_focus_prev(void);
|
||||||
void key_workspace_next(void);
|
void key_workspace_next(void);
|
||||||
@ -80,6 +81,9 @@ void key_toggle_ai(void);
|
|||||||
void key_ai_command(void);
|
void key_ai_command(void);
|
||||||
void key_show_shortcuts(void);
|
void key_show_shortcuts(void);
|
||||||
void key_start_tutorial(void);
|
void key_start_tutorial(void);
|
||||||
|
void key_snap_left(void);
|
||||||
|
void key_snap_right(void);
|
||||||
|
void key_start_demo(void);
|
||||||
|
|
||||||
void tutorial_start(void);
|
void tutorial_start(void);
|
||||||
void tutorial_stop(void);
|
void tutorial_stop(void);
|
||||||
|
|||||||
@ -45,6 +45,7 @@ Client *panel_hit_test_taskbar(Panel *panel, int x, int y);
|
|||||||
void panel_show(Panel *panel);
|
void panel_show(Panel *panel);
|
||||||
void panel_hide(Panel *panel);
|
void panel_hide(Panel *panel);
|
||||||
void panel_toggle(Panel *panel);
|
void panel_toggle(Panel *panel);
|
||||||
|
void panel_raise_all(void);
|
||||||
|
|
||||||
void panel_update_clock(void);
|
void panel_update_clock(void);
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* DWN - Desktop Window Manager
|
* DWN - Desktop Window Manager
|
||||||
* retoor <retoor@molodetz.nl>
|
* retoor <retoor@molodetz.nl>
|
||||||
* System tray widgets (WiFi, Audio, etc.)
|
* System tray widgets (WiFi, Audio, etc.) and XEmbed protocol
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef DWN_SYSTRAY_H
|
#ifndef DWN_SYSTRAY_H
|
||||||
@ -11,6 +11,35 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
#define MAX_WIFI_NETWORKS 20
|
#define MAX_WIFI_NETWORKS 20
|
||||||
|
#define MAX_TRAY_ICONS 32
|
||||||
|
#define TRAY_ICON_SIZE 22
|
||||||
|
#define TRAY_ICON_SPACING 4
|
||||||
|
|
||||||
|
#define SYSTEM_TRAY_REQUEST_DOCK 0
|
||||||
|
#define SYSTEM_TRAY_BEGIN_MESSAGE 1
|
||||||
|
#define SYSTEM_TRAY_CANCEL_MESSAGE 2
|
||||||
|
|
||||||
|
#define XEMBED_EMBEDDED_NOTIFY 0
|
||||||
|
#define XEMBED_WINDOW_ACTIVATE 1
|
||||||
|
#define XEMBED_WINDOW_DEACTIVATE 2
|
||||||
|
#define XEMBED_REQUEST_FOCUS 3
|
||||||
|
#define XEMBED_FOCUS_IN 4
|
||||||
|
#define XEMBED_FOCUS_OUT 5
|
||||||
|
|
||||||
|
#define XEMBED_MAPPED (1 << 0)
|
||||||
|
#define XEMBED_PROTOCOL_VERSION 0
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Window window;
|
||||||
|
int x;
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
bool mapped;
|
||||||
|
} TrayIcon;
|
||||||
|
|
||||||
|
extern TrayIcon tray_icons[MAX_TRAY_ICONS];
|
||||||
|
extern int tray_icon_count;
|
||||||
|
extern Window tray_selection_owner;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char ssid[64];
|
char ssid[64];
|
||||||
@ -114,4 +143,16 @@ void systray_unlock(void);
|
|||||||
BatteryState systray_get_battery_snapshot(void);
|
BatteryState systray_get_battery_snapshot(void);
|
||||||
AudioState systray_get_audio_snapshot(void);
|
AudioState systray_get_audio_snapshot(void);
|
||||||
|
|
||||||
|
void xembed_init(void);
|
||||||
|
void xembed_cleanup(void);
|
||||||
|
bool xembed_dock_icon(Window icon_win);
|
||||||
|
void xembed_remove_icon(Window icon_win);
|
||||||
|
TrayIcon *xembed_find_icon(Window icon_win);
|
||||||
|
void xembed_send_message(Window icon, long message, long detail, long data1, long data2);
|
||||||
|
int xembed_get_icons_width(void);
|
||||||
|
void xembed_render_icons(Panel *panel, int x);
|
||||||
|
void xembed_handle_click(int x, int y, int button);
|
||||||
|
int xembed_hit_test(int x);
|
||||||
|
void xembed_update_icon_state(Window icon_win);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -26,6 +26,7 @@
|
|||||||
<a href="configuration.html">Configuration</a>
|
<a href="configuration.html">Configuration</a>
|
||||||
<a href="ai-features.html">AI Features</a>
|
<a href="ai-features.html">AI Features</a>
|
||||||
<a href="architecture.html">Architecture</a>
|
<a href="architecture.html">Architecture</a>
|
||||||
|
<a href="design-patterns.html">Design Patterns</a>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
|
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
|
||||||
@ -389,6 +390,7 @@ model = google/gemini-2.0-flash-exp:free</code></pre>
|
|||||||
<li><a href="shortcuts.html">Keyboard Shortcuts</a></li>
|
<li><a href="shortcuts.html">Keyboard Shortcuts</a></li>
|
||||||
<li><a href="configuration.html">Configuration</a></li>
|
<li><a href="configuration.html">Configuration</a></li>
|
||||||
<li><a href="architecture.html">Architecture</a></li>
|
<li><a href="architecture.html">Architecture</a></li>
|
||||||
|
<li><a href="design-patterns.html">Design Patterns</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer-section">
|
<div class="footer-section">
|
||||||
|
|||||||
@ -26,6 +26,7 @@
|
|||||||
<a href="configuration.html">Configuration</a>
|
<a href="configuration.html">Configuration</a>
|
||||||
<a href="ai-features.html">AI Features</a>
|
<a href="ai-features.html">AI Features</a>
|
||||||
<a href="architecture.html">Architecture</a>
|
<a href="architecture.html">Architecture</a>
|
||||||
|
<a href="design-patterns.html">Design Patterns</a>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
|
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
|
||||||
@ -62,7 +63,7 @@
|
|||||||
<div style="color: var(--text-muted);">Lines of Code</div>
|
<div style="color: var(--text-muted);">Lines of Code</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div style="font-size: 2rem; font-weight: 700; color: var(--primary);">12</div>
|
<div style="font-size: 2rem; font-weight: 700; color: var(--primary);">13</div>
|
||||||
<div style="color: var(--text-muted);">Core Modules</div>
|
<div style="color: var(--text-muted);">Core Modules</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@ -170,6 +171,11 @@
|
|||||||
<td><code>ai.c</code></td>
|
<td><code>ai.c</code></td>
|
||||||
<td>Async OpenRouter API integration, Exa semantic search</td>
|
<td>Async OpenRouter API integration, Exa semantic search</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Autostart</strong></td>
|
||||||
|
<td><code>autostart.c</code></td>
|
||||||
|
<td>XDG Autostart support, .desktop file parsing, concurrent app launch</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>Util</strong></td>
|
<td><strong>Util</strong></td>
|
||||||
<td><code>util.c</code></td>
|
<td><code>util.c</code></td>
|
||||||
@ -196,6 +202,8 @@
|
|||||||
│ └── config.c
|
│ └── config.c
|
||||||
├── notifications.c (independent)
|
├── notifications.c (independent)
|
||||||
├── ai.c (independent)
|
├── ai.c (independent)
|
||||||
|
├── autostart.c
|
||||||
|
│ └── config.c
|
||||||
└── keys.c
|
└── keys.c
|
||||||
└── config.c</code></pre>
|
└── config.c</code></pre>
|
||||||
</div>
|
</div>
|
||||||
@ -490,6 +498,7 @@ extern DWNState *dwn; // Global singleton</code></pre>
|
|||||||
<li><a href="shortcuts.html">Keyboard Shortcuts</a></li>
|
<li><a href="shortcuts.html">Keyboard Shortcuts</a></li>
|
||||||
<li><a href="configuration.html">Configuration</a></li>
|
<li><a href="configuration.html">Configuration</a></li>
|
||||||
<li><a href="architecture.html">Architecture</a></li>
|
<li><a href="architecture.html">Architecture</a></li>
|
||||||
|
<li><a href="design-patterns.html">Design Patterns</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer-section">
|
<div class="footer-section">
|
||||||
|
|||||||
@ -26,6 +26,7 @@
|
|||||||
<a href="configuration.html">Configuration</a>
|
<a href="configuration.html">Configuration</a>
|
||||||
<a href="ai-features.html">AI Features</a>
|
<a href="ai-features.html">AI Features</a>
|
||||||
<a href="architecture.html">Architecture</a>
|
<a href="architecture.html">Architecture</a>
|
||||||
|
<a href="design-patterns.html">Design Patterns</a>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
|
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
|
||||||
@ -387,6 +388,50 @@ exa_api_key = your-exa-key-here</code></pre>
|
|||||||
<p style="margin: 0;">For better security, use environment variables instead of storing API keys in the config file:
|
<p style="margin: 0;">For better security, use environment variables instead of storing API keys in the config file:
|
||||||
<code>export OPENROUTER_API_KEY=sk-or-v1-...</code></p>
|
<code>export OPENROUTER_API_KEY=sk-or-v1-...</code></p>
|
||||||
</div>
|
</div>
|
||||||
|
<h2 id="autostart" style="margin-top: 3rem;">[autostart] - XDG Autostart</h2>
|
||||||
|
<p style="color: var(--text-muted); margin-bottom: 1.5rem;">
|
||||||
|
Configure automatic application startup. DWN follows the XDG Autostart specification.
|
||||||
|
</p>
|
||||||
|
<div class="table-wrapper">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Option</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><code>enabled</code></td>
|
||||||
|
<td>Enable/disable all autostart functionality (default: <code>true</code>)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>xdg_autostart</code></td>
|
||||||
|
<td>Scan XDG .desktop files from /etc/xdg/autostart and ~/.config/autostart (default: <code>true</code>)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>path</code></td>
|
||||||
|
<td>Additional directory for symlinks/scripts (default: <code>~/.config/dwn/autostart.d</code>)</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="code-header">
|
||||||
|
<span>Example</span>
|
||||||
|
<button class="copy-btn" onclick="copyCode(this)">Copy</button>
|
||||||
|
</div>
|
||||||
|
<pre><code>[autostart]
|
||||||
|
enabled = true
|
||||||
|
xdg_autostart = true
|
||||||
|
path = ~/.config/dwn/autostart.d</code></pre>
|
||||||
|
<p style="color: var(--text-muted); margin-top: 1rem;">
|
||||||
|
<strong>Directories scanned:</strong>
|
||||||
|
</p>
|
||||||
|
<ul style="color: var(--text-muted);">
|
||||||
|
<li><code>/etc/xdg/autostart/</code> - System defaults (nm-applet, blueman, power-manager)</li>
|
||||||
|
<li><code>~/.config/autostart/</code> - User XDG autostart entries</li>
|
||||||
|
<li><code>~/.config/dwn/autostart.d/</code> - DWN-specific symlinks and scripts</li>
|
||||||
|
</ul>
|
||||||
<h2 style="margin-top: 3rem;">Complete Configuration Example</h2>
|
<h2 style="margin-top: 3rem;">Complete Configuration Example</h2>
|
||||||
<div class="code-header">
|
<div class="code-header">
|
||||||
<span>~/.config/dwn/config</span>
|
<span>~/.config/dwn/config</span>
|
||||||
@ -429,7 +474,11 @@ notification_bg = #2a2a3e
|
|||||||
notification_fg = #ffffff
|
notification_fg = #ffffff
|
||||||
[ai]
|
[ai]
|
||||||
model = google/gemini-2.0-flash-exp:free
|
model = google/gemini-2.0-flash-exp:free
|
||||||
# API keys via environment variables recommended</code></pre>
|
# API keys via environment variables recommended
|
||||||
|
[autostart]
|
||||||
|
enabled = true
|
||||||
|
xdg_autostart = true
|
||||||
|
path = ~/.config/dwn/autostart.d</code></pre>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
@ -450,6 +499,7 @@ model = google/gemini-2.0-flash-exp:free
|
|||||||
<li><a href="shortcuts.html">Keyboard Shortcuts</a></li>
|
<li><a href="shortcuts.html">Keyboard Shortcuts</a></li>
|
||||||
<li><a href="configuration.html">Configuration</a></li>
|
<li><a href="configuration.html">Configuration</a></li>
|
||||||
<li><a href="architecture.html">Architecture</a></li>
|
<li><a href="architecture.html">Architecture</a></li>
|
||||||
|
<li><a href="design-patterns.html">Design Patterns</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer-section">
|
<div class="footer-section">
|
||||||
|
|||||||
779
site/design-patterns.html
Normal file
779
site/design-patterns.html
Normal file
@ -0,0 +1,779 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta name="description" content="DWN Design Patterns - Comprehensive documentation of design patterns, architectural decisions, and research sources used in the DWN window manager.">
|
||||||
|
<meta name="keywords" content="design patterns, C programming, opaque pointer, vtable, factory pattern, observer pattern, EWMH, ICCCM, XEmbed">
|
||||||
|
<title>Design Patterns - DWN Window Manager</title>
|
||||||
|
<link rel="stylesheet" href="css/style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
<nav>
|
||||||
|
<a href="index.html" class="logo">
|
||||||
|
<span class="logo-icon">D</span>
|
||||||
|
<span>DWN</span>
|
||||||
|
</a>
|
||||||
|
<ul class="nav-links">
|
||||||
|
<li><a href="index.html">Home</a></li>
|
||||||
|
<li><a href="features.html">Features</a></li>
|
||||||
|
<li><a href="installation.html">Install</a></li>
|
||||||
|
<li class="dropdown">
|
||||||
|
<a href="documentation.html">Docs</a>
|
||||||
|
<div class="dropdown-menu">
|
||||||
|
<a href="documentation.html">Getting Started</a>
|
||||||
|
<a href="shortcuts.html">Keyboard Shortcuts</a>
|
||||||
|
<a href="configuration.html">Configuration</a>
|
||||||
|
<a href="ai-features.html">AI Features</a>
|
||||||
|
<a href="architecture.html">Architecture</a>
|
||||||
|
<a href="design-patterns.html" class="active">Design Patterns</a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
|
||||||
|
</ul>
|
||||||
|
<div class="nav-toggle" onclick="toggleNav()">
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
<span></span>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
<section class="hero hero-small">
|
||||||
|
<div class="container">
|
||||||
|
<h1>Design Patterns & Architecture</h1>
|
||||||
|
<p class="subtitle">
|
||||||
|
Comprehensive documentation of the design patterns, architectural decisions,
|
||||||
|
and research that shaped DWN's implementation.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="section">
|
||||||
|
<div class="container docs-container">
|
||||||
|
<aside class="docs-sidebar">
|
||||||
|
<h3>Contents</h3>
|
||||||
|
<ul>
|
||||||
|
<li><a href="#overview">Overview</a></li>
|
||||||
|
<li><a href="#c-patterns">C Design Patterns</a></li>
|
||||||
|
<li><a href="#opaque-pointer">Opaque Pointer</a></li>
|
||||||
|
<li><a href="#goto-cleanup">Goto Cleanup</a></li>
|
||||||
|
<li><a href="#vtable">Vtable Polymorphism</a></li>
|
||||||
|
<li><a href="#factory">Factory Pattern</a></li>
|
||||||
|
<li><a href="#observer">Observer Pattern</a></li>
|
||||||
|
<li><a href="#singleton">Singleton Pattern</a></li>
|
||||||
|
<li><a href="#double-fork">Double Fork Daemon</a></li>
|
||||||
|
<li><a href="#x11-protocols">X11 Protocols</a></li>
|
||||||
|
<li><a href="#ewmh">EWMH Specification</a></li>
|
||||||
|
<li><a href="#icccm">ICCCM Standard</a></li>
|
||||||
|
<li><a href="#xembed">XEmbed Protocol</a></li>
|
||||||
|
<li><a href="#systray">System Tray Protocol</a></li>
|
||||||
|
<li><a href="#xdg">XDG Specifications</a></li>
|
||||||
|
<li><a href="#async">Async Patterns</a></li>
|
||||||
|
<li><a href="#modular">Modular Architecture</a></li>
|
||||||
|
<li><a href="#defensive">Defensive Programming</a></li>
|
||||||
|
<li><a href="#sources">Research Sources</a></li>
|
||||||
|
</ul>
|
||||||
|
</aside>
|
||||||
|
<div class="docs-content">
|
||||||
|
<h2 id="overview">Overview</h2>
|
||||||
|
<p>
|
||||||
|
DWN is built using professional C design patterns that provide encapsulation,
|
||||||
|
modularity, and maintainability without the overhead of C++. This document
|
||||||
|
details the patterns used, the rationale behind architectural decisions,
|
||||||
|
and links to authoritative sources.
|
||||||
|
</p>
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<strong class="alert-title">Design Philosophy</strong>
|
||||||
|
<p style="margin: 0;">
|
||||||
|
DWN prioritizes simplicity, readability, and defensive programming.
|
||||||
|
Each module has a single responsibility with well-defined interfaces.
|
||||||
|
The codebase follows the principle: "Simple is better than complex."
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 id="c-patterns" style="margin-top: 3rem;">C Design Patterns</h2>
|
||||||
|
<p style="color: var(--text-muted); margin-bottom: 2rem;">
|
||||||
|
While C lacks native object-oriented features, these patterns provide
|
||||||
|
equivalent functionality with minimal overhead.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3 id="opaque-pointer">Opaque Pointer Pattern</h3>
|
||||||
|
<div class="card" style="margin-bottom: 2rem;">
|
||||||
|
<h4>Purpose</h4>
|
||||||
|
<p>Hide implementation details from API consumers, enabling changes without recompilation.</p>
|
||||||
|
<h4>How It Works</h4>
|
||||||
|
<p>The header declares a pointer to an incomplete type. The struct definition
|
||||||
|
exists only in the implementation file, preventing direct member access.</p>
|
||||||
|
<div class="code-header">
|
||||||
|
<span>Header (public)</span>
|
||||||
|
</div>
|
||||||
|
<pre><code>typedef struct config_t *Config;
|
||||||
|
Config config_create(void);
|
||||||
|
void config_destroy(Config cfg);</code></pre>
|
||||||
|
<div class="code-header">
|
||||||
|
<span>Implementation (private)</span>
|
||||||
|
</div>
|
||||||
|
<pre><code>struct config_t {
|
||||||
|
int border_width;
|
||||||
|
char terminal[128];
|
||||||
|
};</code></pre>
|
||||||
|
<h4>Benefits</h4>
|
||||||
|
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
|
||||||
|
<li>API consumers cannot access internal fields directly</li>
|
||||||
|
<li>Implementation can change without breaking client code</li>
|
||||||
|
<li>Enforces encapsulation at compile time</li>
|
||||||
|
</ul>
|
||||||
|
<h4>Used In</h4>
|
||||||
|
<p style="color: var(--text-muted);">
|
||||||
|
<code>config.c</code> - Configuration management
|
||||||
|
</p>
|
||||||
|
<h4>Research Sources</h4>
|
||||||
|
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
|
||||||
|
<li><a href="https://interrupt.memfault.com/blog/opaque-pointers" target="_blank">Practical Design Patterns: Opaque Pointers and Objects in C</a> - Memfault</li>
|
||||||
|
<li><a href="https://en.wikipedia.org/wiki/Opaque_pointer" target="_blank">Opaque Pointer</a> - Wikipedia</li>
|
||||||
|
<li><a href="https://wiki.sei.cmu.edu/confluence/display/c/DCL12-C.+Implement+abstract+data+types+using+opaque+types" target="_blank">DCL12-C: Implement abstract data types using opaque types</a> - SEI CERT C Coding Standard</li>
|
||||||
|
<li><a href="https://blog.mbedded.ninja/programming/design-patterns/opaque-pointers/" target="_blank">Opaque Pointers</a> - mbedded.ninja</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 id="goto-cleanup">Goto Cleanup Pattern</h3>
|
||||||
|
<div class="card" style="margin-bottom: 2rem;">
|
||||||
|
<h4>Purpose</h4>
|
||||||
|
<p>Centralize resource cleanup in functions that acquire multiple resources,
|
||||||
|
preventing memory leaks and ensuring proper deallocation on all code paths.</p>
|
||||||
|
<h4>How It Works</h4>
|
||||||
|
<p>Resources are initialized to safe values (NULL). On error, execution jumps
|
||||||
|
to a cleanup label. The cleanup section safely releases all resources.</p>
|
||||||
|
<div class="code-header">
|
||||||
|
<span>Example</span>
|
||||||
|
</div>
|
||||||
|
<pre><code>int process_file(const char *path) {
|
||||||
|
char *buf = NULL;
|
||||||
|
FILE *f = NULL;
|
||||||
|
int status = -1;
|
||||||
|
|
||||||
|
buf = malloc(1024);
|
||||||
|
if (!buf) goto cleanup;
|
||||||
|
|
||||||
|
f = fopen(path, "r");
|
||||||
|
if (!f) goto cleanup;
|
||||||
|
|
||||||
|
// ... processing ...
|
||||||
|
status = 0;
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
free(buf); // safe: free(NULL) is no-op
|
||||||
|
if (f) fclose(f);
|
||||||
|
return status;
|
||||||
|
}</code></pre>
|
||||||
|
<h4>Benefits</h4>
|
||||||
|
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
|
||||||
|
<li>Single cleanup point prevents code duplication</li>
|
||||||
|
<li>All error paths properly release resources</li>
|
||||||
|
<li>Used extensively in Linux kernel and SQLite</li>
|
||||||
|
</ul>
|
||||||
|
<h4>Used In</h4>
|
||||||
|
<p style="color: var(--text-muted);">
|
||||||
|
<code>config.c</code>, <code>news.c</code>, <code>ai.c</code> - File and network operations
|
||||||
|
</p>
|
||||||
|
<h4>Research Sources</h4>
|
||||||
|
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
|
||||||
|
<li><a href="https://wiki.sei.cmu.edu/confluence/display/c/MEM12-C.+Consider+using+a+goto+chain+when+leaving+a+function+on+error+when+using+and+releasing+resources" target="_blank">MEM12-C: Using goto chain for error handling</a> - SEI CERT C Coding Standard</li>
|
||||||
|
<li><a href="https://eli.thegreenplace.net/2009/04/27/using-goto-for-error-handling-in-c" target="_blank">Using goto for error handling in C</a> - Eli Bendersky</li>
|
||||||
|
<li><a href="https://www.geeksforgeeks.org/c/using-goto-for-exception-handling-in-c/" target="_blank">Using goto for Exception Handling in C</a> - GeeksforGeeks</li>
|
||||||
|
<li><a href="https://ayende.com/blog/183521-C/error-handling-via-goto-in-c" target="_blank">Error handling via GOTO in C</a> - Ayende Rahien</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 id="vtable">Vtable Polymorphism</h3>
|
||||||
|
<div class="card" style="margin-bottom: 2rem;">
|
||||||
|
<h4>Purpose</h4>
|
||||||
|
<p>Enable runtime polymorphism in C using function pointer tables,
|
||||||
|
allowing different implementations to share a common interface.</p>
|
||||||
|
<h4>How It Works</h4>
|
||||||
|
<p>A struct of function pointers (vtable) defines the interface. Objects contain
|
||||||
|
a pointer to their vtable. Calling through the vtable invokes the correct implementation.</p>
|
||||||
|
<div class="code-header">
|
||||||
|
<span>Example</span>
|
||||||
|
</div>
|
||||||
|
<pre><code>typedef struct Widget Widget;
|
||||||
|
typedef struct {
|
||||||
|
void (*draw)(Widget *self);
|
||||||
|
void (*destroy)(Widget *self);
|
||||||
|
} WidgetVtable;
|
||||||
|
|
||||||
|
struct Widget {
|
||||||
|
const WidgetVtable *vtable;
|
||||||
|
int x, y, width, height;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Usage: widget->vtable->draw(widget);</code></pre>
|
||||||
|
<h4>Benefits</h4>
|
||||||
|
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
|
||||||
|
<li>Runtime dispatch without language support</li>
|
||||||
|
<li>New implementations added without modifying existing code</li>
|
||||||
|
<li>Same pattern used by C++ compilers internally</li>
|
||||||
|
</ul>
|
||||||
|
<h4>Used In</h4>
|
||||||
|
<p style="color: var(--text-muted);">
|
||||||
|
<code>panel.c</code> - Widget rendering system
|
||||||
|
</p>
|
||||||
|
<h4>Research Sources</h4>
|
||||||
|
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
|
||||||
|
<li><a href="https://en.wikipedia.org/wiki/Virtual_method_table" target="_blank">Virtual Method Table</a> - Wikipedia</li>
|
||||||
|
<li><a href="https://embeddedartistry.com/fieldatlas/technique-inheritance-and-polymorphism-in-c/" target="_blank">Inheritance and Polymorphism in C</a> - Embedded Artistry</li>
|
||||||
|
<li><a href="https://www.state-machine.com/doc/AN_Simple_OOP_in_C.pdf" target="_blank">Object-Oriented Programming in C</a> - Quantum Leaps (PDF)</li>
|
||||||
|
<li><a href="https://www.embedded.com/programming-embedded-systems-polymorphism-in-c-2/" target="_blank">Programming embedded systems: polymorphism in C</a> - Embedded.com</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 id="factory">Factory Pattern</h3>
|
||||||
|
<div class="card" style="margin-bottom: 2rem;">
|
||||||
|
<h4>Purpose</h4>
|
||||||
|
<p>Encapsulate object creation logic, allowing the system to create objects
|
||||||
|
without specifying their exact types.</p>
|
||||||
|
<h4>How It Works</h4>
|
||||||
|
<p>A factory function takes parameters describing what to create and returns
|
||||||
|
a pointer to the appropriate object type.</p>
|
||||||
|
<div class="code-header">
|
||||||
|
<span>Example</span>
|
||||||
|
</div>
|
||||||
|
<pre><code>typedef enum { WIDGET_BUTTON, WIDGET_LABEL } WidgetType;
|
||||||
|
|
||||||
|
Widget *widget_create(WidgetType type) {
|
||||||
|
switch (type) {
|
||||||
|
case WIDGET_BUTTON: return button_create();
|
||||||
|
case WIDGET_LABEL: return label_create();
|
||||||
|
default: return NULL;
|
||||||
|
}
|
||||||
|
}</code></pre>
|
||||||
|
<h4>Benefits</h4>
|
||||||
|
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
|
||||||
|
<li>Centralizes object creation logic</li>
|
||||||
|
<li>New types added without changing client code</li>
|
||||||
|
<li>Supports Open/Closed Principle</li>
|
||||||
|
</ul>
|
||||||
|
<h4>Used In</h4>
|
||||||
|
<p style="color: var(--text-muted);">
|
||||||
|
<code>config.c</code>, <code>client.c</code>, <code>workspace.c</code> - Object lifecycle management
|
||||||
|
</p>
|
||||||
|
<h4>Research Sources</h4>
|
||||||
|
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
|
||||||
|
<li><a href="https://refactoring.guru/design-patterns/factory-method" target="_blank">Factory Method Pattern</a> - Refactoring Guru</li>
|
||||||
|
<li><a href="https://en.wikipedia.org/wiki/Factory_method_pattern" target="_blank">Factory Method Pattern</a> - Wikipedia</li>
|
||||||
|
<li><a href="https://sourcemaking.com/design_patterns" target="_blank">Design Patterns</a> - SourceMaking</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 id="observer">Observer Pattern</h3>
|
||||||
|
<div class="card" style="margin-bottom: 2rem;">
|
||||||
|
<h4>Purpose</h4>
|
||||||
|
<p>Implement event-driven communication where subjects notify observers
|
||||||
|
of state changes without tight coupling.</p>
|
||||||
|
<h4>How It Works</h4>
|
||||||
|
<p>Observers register callback functions with subjects. When events occur,
|
||||||
|
the subject iterates through registered callbacks and invokes them.</p>
|
||||||
|
<div class="code-header">
|
||||||
|
<span>Example</span>
|
||||||
|
</div>
|
||||||
|
<pre><code>typedef void (*EventCallback)(void *data);
|
||||||
|
|
||||||
|
void events_register(EventType type, EventCallback cb, void *data);
|
||||||
|
void events_emit(EventType type);
|
||||||
|
|
||||||
|
// Notification triggers all registered callbacks</code></pre>
|
||||||
|
<h4>Benefits</h4>
|
||||||
|
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
|
||||||
|
<li>Loose coupling between event sources and handlers</li>
|
||||||
|
<li>Observers added/removed without modifying subjects</li>
|
||||||
|
<li>Foundation of event-driven programming</li>
|
||||||
|
</ul>
|
||||||
|
<h4>Used In</h4>
|
||||||
|
<p style="color: var(--text-muted);">
|
||||||
|
<code>keys.c</code> - Keyboard event handling, <code>notifications.c</code> - D-Bus signals
|
||||||
|
</p>
|
||||||
|
<h4>Research Sources</h4>
|
||||||
|
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
|
||||||
|
<li><a href="https://refactoring.guru/design-patterns/observer" target="_blank">Observer Pattern</a> - Refactoring Guru</li>
|
||||||
|
<li><a href="https://en.wikipedia.org/wiki/Observer_pattern" target="_blank">Observer Pattern</a> - Wikipedia</li>
|
||||||
|
<li><a href="https://learn.microsoft.com/en-us/dotnet/standard/events/observer-design-pattern" target="_blank">Observer Design Pattern</a> - Microsoft Learn</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 id="singleton">Singleton / Global State</h3>
|
||||||
|
<div class="card" style="margin-bottom: 2rem;">
|
||||||
|
<h4>Purpose</h4>
|
||||||
|
<p>Provide a single, globally accessible instance of the window manager state,
|
||||||
|
simplifying module communication.</p>
|
||||||
|
<h4>Implementation in DWN</h4>
|
||||||
|
<p>DWN uses a single <code>DWNState</code> structure accessible via the global
|
||||||
|
<code>dwn</code> pointer. This is appropriate for a window manager where
|
||||||
|
exactly one instance exists per X11 session.</p>
|
||||||
|
<div class="code-header">
|
||||||
|
<span>Example</span>
|
||||||
|
</div>
|
||||||
|
<pre><code>// Global state pointer
|
||||||
|
extern DWNState *dwn;
|
||||||
|
|
||||||
|
// Access from any module
|
||||||
|
if (dwn != NULL && dwn->config != NULL) {
|
||||||
|
return dwn->config->terminal;
|
||||||
|
}</code></pre>
|
||||||
|
<h4>Rationale</h4>
|
||||||
|
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
|
||||||
|
<li>Window manager is inherently a singleton per X session</li>
|
||||||
|
<li>Simplifies inter-module communication</li>
|
||||||
|
<li>All state centralized for easier debugging</li>
|
||||||
|
</ul>
|
||||||
|
<h4>Research Sources</h4>
|
||||||
|
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
|
||||||
|
<li><a href="https://refactoring.guru/design-patterns/singleton" target="_blank">Singleton Pattern</a> - Refactoring Guru</li>
|
||||||
|
<li><a href="https://gameprogrammingpatterns.com/singleton.html" target="_blank">Singleton</a> - Game Programming Patterns</li>
|
||||||
|
<li><a href="https://en.wikipedia.org/wiki/Singleton_pattern" target="_blank">Singleton Pattern</a> - Wikipedia</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 id="double-fork">Double Fork Daemon Pattern</h3>
|
||||||
|
<div class="card" style="margin-bottom: 2rem;">
|
||||||
|
<h4>Purpose</h4>
|
||||||
|
<p>Launch background processes that are fully detached from the parent,
|
||||||
|
preventing zombie processes and terminal reattachment.</p>
|
||||||
|
<h4>How It Works</h4>
|
||||||
|
<p>The pattern uses two fork() calls: the first creates a child that calls
|
||||||
|
setsid() to become a session leader, then forks again. The grandchild
|
||||||
|
cannot reacquire a controlling terminal.</p>
|
||||||
|
<div class="code-header">
|
||||||
|
<span>Implementation in DWN</span>
|
||||||
|
</div>
|
||||||
|
<pre><code>int spawn_async(const char *cmd) {
|
||||||
|
pid_t pid = fork();
|
||||||
|
if (pid == 0) {
|
||||||
|
setsid(); // New session
|
||||||
|
pid_t pid2 = fork(); // Second fork
|
||||||
|
if (pid2 == 0) {
|
||||||
|
execl("/bin/sh", "sh", "-c", cmd, NULL);
|
||||||
|
_exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
_exit(EXIT_SUCCESS); // Intermediate exits
|
||||||
|
}
|
||||||
|
waitpid(pid, &status, 0); // Only wait for intermediate
|
||||||
|
return 0;
|
||||||
|
}</code></pre>
|
||||||
|
<h4>Benefits</h4>
|
||||||
|
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
|
||||||
|
<li>Process fully detached from parent</li>
|
||||||
|
<li>No zombie processes (intermediate is reaped immediately)</li>
|
||||||
|
<li>Cannot reacquire controlling terminal</li>
|
||||||
|
<li>Non-blocking for the caller</li>
|
||||||
|
</ul>
|
||||||
|
<h4>Used In</h4>
|
||||||
|
<p style="color: var(--text-muted);">
|
||||||
|
<code>util.c</code> - spawn_async(), <code>autostart.c</code>, <code>applauncher.c</code>
|
||||||
|
</p>
|
||||||
|
<h4>Research Sources</h4>
|
||||||
|
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
|
||||||
|
<li><a href="https://0xjet.github.io/3OHA/2022/04/11/post.html" target="_blank">UNIX daemonization and the double fork</a> - Juan Tapiador</li>
|
||||||
|
<li><a href="https://www.digitalbunker.dev/understanding-daemons-unix/" target="_blank">Understanding Daemons</a> - Digital Bunker</li>
|
||||||
|
<li><a href="https://lloydrochester.com/post/c/unix-daemon-example/" target="_blank">Daemon Example in C</a> - Lloyd Rochester</li>
|
||||||
|
<li><a href="https://goral.net.pl/post/double-fork/" target="_blank">Double Fork</a> - Michal Goral</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 id="x11-protocols" style="margin-top: 3rem;">X11 Window Manager Protocols</h2>
|
||||||
|
<p style="color: var(--text-muted); margin-bottom: 2rem;">
|
||||||
|
DWN implements several freedesktop.org specifications for cross-desktop compatibility.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3 id="ewmh">Extended Window Manager Hints (EWMH)</h3>
|
||||||
|
<div class="card" style="margin-bottom: 2rem;">
|
||||||
|
<h4>Purpose</h4>
|
||||||
|
<p>Define interactions between window managers, compositing managers, applications,
|
||||||
|
and desktop utilities in a standardized way.</p>
|
||||||
|
<h4>Key Features Implemented</h4>
|
||||||
|
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
|
||||||
|
<li><code>_NET_SUPPORTED</code> - List of supported hints</li>
|
||||||
|
<li><code>_NET_CLIENT_LIST</code> - List of managed windows</li>
|
||||||
|
<li><code>_NET_CURRENT_DESKTOP</code> - Active workspace</li>
|
||||||
|
<li><code>_NET_WM_STATE</code> - Window states (fullscreen, maximized)</li>
|
||||||
|
<li><code>_NET_ACTIVE_WINDOW</code> - Currently focused window</li>
|
||||||
|
<li><code>_NET_WM_WINDOW_TYPE</code> - Window type classification</li>
|
||||||
|
</ul>
|
||||||
|
<h4>Implementation</h4>
|
||||||
|
<p style="color: var(--text-muted);">
|
||||||
|
<code>atoms.c</code> manages X11 atom creation and EWMH property updates.
|
||||||
|
Properties are updated on window focus changes, workspace switches, and state changes.
|
||||||
|
</p>
|
||||||
|
<h4>Research Sources</h4>
|
||||||
|
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
|
||||||
|
<li><a href="https://specifications.freedesktop.org/wm/latest/" target="_blank">Extended Window Manager Hints Specification</a> - freedesktop.org</li>
|
||||||
|
<li><a href="https://en.wikipedia.org/wiki/Extended_Window_Manager_Hints" target="_blank">Extended Window Manager Hints</a> - Wikipedia</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 id="icccm">Inter-Client Communication Conventions Manual (ICCCM)</h3>
|
||||||
|
<div class="card" style="margin-bottom: 2rem;">
|
||||||
|
<h4>Purpose</h4>
|
||||||
|
<p>Define low-level conventions for X11 client communication, including
|
||||||
|
selections, window management, and session management.</p>
|
||||||
|
<h4>Key Features Implemented</h4>
|
||||||
|
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
|
||||||
|
<li><code>WM_PROTOCOLS</code> - Window close handling</li>
|
||||||
|
<li><code>WM_DELETE_WINDOW</code> - Graceful window closing</li>
|
||||||
|
<li><code>WM_NAME</code> / <code>_NET_WM_NAME</code> - Window titles</li>
|
||||||
|
<li><code>WM_CLASS</code> - Application classification</li>
|
||||||
|
<li><code>WM_HINTS</code> - Window hints (urgency, input model)</li>
|
||||||
|
</ul>
|
||||||
|
<h4>Research Sources</h4>
|
||||||
|
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
|
||||||
|
<li><a href="https://x.org/releases/X11R7.6/doc/xorg-docs/specs/ICCCM/icccm.html" target="_blank">Inter-Client Communication Conventions Manual</a> - X.Org</li>
|
||||||
|
<li><a href="https://tronche.com/gui/x/icccm/" target="_blank">ICCCM Reference</a> - Christophe Tronche</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 id="xembed">XEmbed Protocol</h3>
|
||||||
|
<div class="card" style="margin-bottom: 2rem;">
|
||||||
|
<h4>Purpose</h4>
|
||||||
|
<p>Enable embedding of controls from one application into another,
|
||||||
|
forming the basis of the system tray implementation.</p>
|
||||||
|
<h4>How It Works</h4>
|
||||||
|
<p>The embedder (DWN panel) acts as a window manager for embedded clients.
|
||||||
|
Client windows are reparented into the embedder, and events are coordinated
|
||||||
|
through XEMBED messages.</p>
|
||||||
|
<h4>Key Messages</h4>
|
||||||
|
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
|
||||||
|
<li><code>XEMBED_EMBEDDED_NOTIFY</code> - Sent when embedding completes</li>
|
||||||
|
<li><code>XEMBED_FOCUS_IN/OUT</code> - Focus coordination</li>
|
||||||
|
<li><code>XEMBED_WINDOW_ACTIVATE</code> - Window activation</li>
|
||||||
|
</ul>
|
||||||
|
<h4>Implementation</h4>
|
||||||
|
<p style="color: var(--text-muted);">
|
||||||
|
<code>systray.c</code> implements the embedder side, reparenting tray icons
|
||||||
|
and forwarding click events.
|
||||||
|
</p>
|
||||||
|
<h4>Research Sources</h4>
|
||||||
|
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
|
||||||
|
<li><a href="https://specifications.freedesktop.org/xembed-spec/latest/" target="_blank">XEmbed Protocol Specification</a> - freedesktop.org</li>
|
||||||
|
<li><a href="https://www.freedesktop.org/wiki/Specifications/xembed-spec/" target="_blank">XEmbed Spec Wiki</a> - freedesktop.org</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 id="systray">System Tray Protocol</h3>
|
||||||
|
<div class="card" style="margin-bottom: 2rem;">
|
||||||
|
<h4>Purpose</h4>
|
||||||
|
<p>Enable applications to display status icons in a desktop panel,
|
||||||
|
providing a standardized notification area.</p>
|
||||||
|
<h4>How It Works</h4>
|
||||||
|
<p>DWN acquires the <code>_NET_SYSTEM_TRAY_S0</code> selection to become the
|
||||||
|
tray manager. Applications send <code>SYSTEM_TRAY_REQUEST_DOCK</code> messages
|
||||||
|
to dock their icons.</p>
|
||||||
|
<h4>Docking Process</h4>
|
||||||
|
<ol style="padding-left: 1.25rem; color: var(--text-muted);">
|
||||||
|
<li>DWN acquires <code>_NET_SYSTEM_TRAY_S0</code> selection</li>
|
||||||
|
<li>Application sends <code>SYSTEM_TRAY_REQUEST_DOCK</code> client message</li>
|
||||||
|
<li>DWN creates embedding window and reparents icon</li>
|
||||||
|
<li>DWN sends <code>XEMBED_EMBEDDED_NOTIFY</code> to icon</li>
|
||||||
|
<li>Click events forwarded to icon window</li>
|
||||||
|
</ol>
|
||||||
|
<h4>Supported Applications</h4>
|
||||||
|
<p style="color: var(--text-muted);">
|
||||||
|
nm-applet, blueman-applet, Telegram, pasystray, udiskie, and any XEmbed-compatible tray icon.
|
||||||
|
</p>
|
||||||
|
<h4>Research Sources</h4>
|
||||||
|
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
|
||||||
|
<li><a href="https://specifications.freedesktop.org/systemtray-spec/systemtray-spec-0.3.html" target="_blank">System Tray Protocol Specification</a> - freedesktop.org</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 id="xdg" style="margin-top: 3rem;">XDG Specifications</h2>
|
||||||
|
|
||||||
|
<h3>Desktop Entry Specification</h3>
|
||||||
|
<div class="card" style="margin-bottom: 2rem;">
|
||||||
|
<h4>Purpose</h4>
|
||||||
|
<p>Standard format for application metadata files (.desktop files) used
|
||||||
|
by application launchers and autostart systems.</p>
|
||||||
|
<h4>Key Fields Parsed</h4>
|
||||||
|
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
|
||||||
|
<li><code>Exec</code> - Command to execute</li>
|
||||||
|
<li><code>TryExec</code> - Check if binary exists</li>
|
||||||
|
<li><code>Hidden</code> - Entry is disabled</li>
|
||||||
|
<li><code>OnlyShowIn</code> / <code>NotShowIn</code> - Desktop environment filters</li>
|
||||||
|
<li><code>Terminal</code> - Run in terminal</li>
|
||||||
|
</ul>
|
||||||
|
<h4>Implementation</h4>
|
||||||
|
<p style="color: var(--text-muted);">
|
||||||
|
<code>applauncher.c</code> parses .desktop files for the application menu.
|
||||||
|
<code>autostart.c</code> parses autostart entries.
|
||||||
|
</p>
|
||||||
|
<h4>Research Sources</h4>
|
||||||
|
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
|
||||||
|
<li><a href="https://specifications.freedesktop.org/desktop-entry-spec/latest/" target="_blank">Desktop Entry Specification</a> - freedesktop.org</li>
|
||||||
|
<li><a href="https://wiki.archlinux.org/title/Desktop_entries" target="_blank">Desktop Entries</a> - ArchWiki</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Desktop Application Autostart Specification</h3>
|
||||||
|
<div class="card" style="margin-bottom: 2rem;">
|
||||||
|
<h4>Purpose</h4>
|
||||||
|
<p>Define standard locations and format for applications that should
|
||||||
|
start automatically when the user logs in.</p>
|
||||||
|
<h4>Directories Scanned</h4>
|
||||||
|
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
|
||||||
|
<li><code>/etc/xdg/autostart/</code> - System-wide autostart</li>
|
||||||
|
<li><code>~/.config/autostart/</code> - User autostart</li>
|
||||||
|
<li><code>~/.config/dwn/autostart.d/</code> - DWN-specific (symlinks)</li>
|
||||||
|
</ul>
|
||||||
|
<h4>Key Behavior</h4>
|
||||||
|
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
|
||||||
|
<li>User entries override system entries with same filename</li>
|
||||||
|
<li><code>Hidden=true</code> disables an entry</li>
|
||||||
|
<li><code>TryExec</code> prevents running if binary missing</li>
|
||||||
|
</ul>
|
||||||
|
<h4>Research Sources</h4>
|
||||||
|
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
|
||||||
|
<li><a href="https://specifications.freedesktop.org/autostart-spec/autostart-spec-latest.html" target="_blank">Desktop Application Autostart Specification</a> - freedesktop.org</li>
|
||||||
|
<li><a href="https://wiki.archlinux.org/title/XDG_Autostart" target="_blank">XDG Autostart</a> - ArchWiki</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Desktop Notifications Specification</h3>
|
||||||
|
<div class="card" style="margin-bottom: 2rem;">
|
||||||
|
<h4>Purpose</h4>
|
||||||
|
<p>Standard D-Bus interface for applications to display passive notifications
|
||||||
|
to users without blocking.</p>
|
||||||
|
<h4>D-Bus Interface</h4>
|
||||||
|
<p style="color: var(--text-muted);">
|
||||||
|
DWN implements <code>org.freedesktop.Notifications</code> on the session bus
|
||||||
|
at path <code>/org/freedesktop/Notifications</code>.
|
||||||
|
</p>
|
||||||
|
<h4>Key Methods</h4>
|
||||||
|
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
|
||||||
|
<li><code>Notify</code> - Display a notification</li>
|
||||||
|
<li><code>CloseNotification</code> - Dismiss a notification</li>
|
||||||
|
<li><code>GetCapabilities</code> - Query supported features</li>
|
||||||
|
<li><code>GetServerInformation</code> - Server metadata</li>
|
||||||
|
</ul>
|
||||||
|
<h4>Research Sources</h4>
|
||||||
|
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
|
||||||
|
<li><a href="https://specifications.freedesktop.org/notification-spec/latest/" target="_blank">Desktop Notifications Specification</a> - freedesktop.org</li>
|
||||||
|
<li><a href="https://wiki.archlinux.org/title/Desktop_notifications" target="_blank">Desktop Notifications</a> - ArchWiki</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 id="async" style="margin-top: 3rem;">Async Programming Patterns</h2>
|
||||||
|
|
||||||
|
<h3>libcurl Multi Interface</h3>
|
||||||
|
<div class="card" style="margin-bottom: 2rem;">
|
||||||
|
<h4>Purpose</h4>
|
||||||
|
<p>Perform HTTP requests asynchronously without blocking the main event loop,
|
||||||
|
essential for AI features and news fetching.</p>
|
||||||
|
<h4>How It Works</h4>
|
||||||
|
<p>Instead of blocking on network I/O, the multi interface allows the main
|
||||||
|
loop to poll for completion. <code>curl_multi_perform()</code> advances
|
||||||
|
transfers incrementally.</p>
|
||||||
|
<div class="code-header">
|
||||||
|
<span>Integration Pattern</span>
|
||||||
|
</div>
|
||||||
|
<pre><code>// In event loop (16ms intervals)
|
||||||
|
curl_multi_perform(multi_handle, &running);
|
||||||
|
CURLMsg *msg = curl_multi_info_read(multi_handle, &msgs_left);
|
||||||
|
if (msg && msg->msg == CURLMSG_DONE) {
|
||||||
|
// Request completed, process response
|
||||||
|
}</code></pre>
|
||||||
|
<h4>Implementation</h4>
|
||||||
|
<p style="color: var(--text-muted);">
|
||||||
|
<code>ai.c</code> uses curl_multi for OpenRouter API calls.
|
||||||
|
Responses processed in <code>ai_process_pending()</code> called from main loop.
|
||||||
|
</p>
|
||||||
|
<h4>Research Sources</h4>
|
||||||
|
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
|
||||||
|
<li><a href="https://curl.se/libcurl/c/libcurl-multi.html" target="_blank">libcurl multi interface overview</a> - curl.se</li>
|
||||||
|
<li><a href="https://curl.se/libcurl/c/libcurl-tutorial.html" target="_blank">libcurl programming tutorial</a> - curl.se</li>
|
||||||
|
<li><a href="https://curl.se/libcurl/c/multi-app.html" target="_blank">multi-app.c example</a> - curl.se</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>pthread for Background I/O</h3>
|
||||||
|
<div class="card" style="margin-bottom: 2rem;">
|
||||||
|
<h4>Purpose</h4>
|
||||||
|
<p>Offload blocking operations to separate threads when async APIs
|
||||||
|
are unavailable or impractical.</p>
|
||||||
|
<h4>Implementation</h4>
|
||||||
|
<p style="color: var(--text-muted);">
|
||||||
|
<code>news.c</code> spawns a detached thread for RSS fetching.
|
||||||
|
Mutex guards shared state, atomic flags prevent concurrent fetches.
|
||||||
|
</p>
|
||||||
|
<div class="code-header">
|
||||||
|
<span>Pattern</span>
|
||||||
|
</div>
|
||||||
|
<pre><code>static pthread_mutex_t news_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
static atomic_int fetch_running = 0;
|
||||||
|
|
||||||
|
void news_fetch_async(void) {
|
||||||
|
if (atomic_load(&fetch_running)) return;
|
||||||
|
atomic_store(&fetch_running, 1);
|
||||||
|
pthread_create(&fetch_thread, NULL, fetch_thread_func, NULL);
|
||||||
|
}</code></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 id="modular" style="margin-top: 3rem;">Modular Architecture</h2>
|
||||||
|
<div class="card" style="margin-bottom: 2rem;">
|
||||||
|
<h4>Design Principle</h4>
|
||||||
|
<p>Each module has a single responsibility with well-defined interfaces.
|
||||||
|
Modules communicate through the global <code>dwn</code> state and
|
||||||
|
function calls, never through shared mutable state.</p>
|
||||||
|
<h4>Module Structure</h4>
|
||||||
|
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
|
||||||
|
<li><code>include/module.h</code> - Public API declarations</li>
|
||||||
|
<li><code>src/module.c</code> - Private implementation</li>
|
||||||
|
<li>Static functions for internal logic</li>
|
||||||
|
<li>module_init() / module_cleanup() lifecycle</li>
|
||||||
|
</ul>
|
||||||
|
<h4>Module Responsibilities</h4>
|
||||||
|
<div class="table-wrapper">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Module</th>
|
||||||
|
<th>Responsibility</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>main.c</td><td>Event loop, initialization orchestration</td></tr>
|
||||||
|
<tr><td>client.c</td><td>Window management, focus handling</td></tr>
|
||||||
|
<tr><td>workspace.c</td><td>Virtual desktop management</td></tr>
|
||||||
|
<tr><td>layout.c</td><td>Tiling algorithms</td></tr>
|
||||||
|
<tr><td>panel.c</td><td>UI panels and widgets</td></tr>
|
||||||
|
<tr><td>systray.c</td><td>System tray protocol</td></tr>
|
||||||
|
<tr><td>notifications.c</td><td>D-Bus notification daemon</td></tr>
|
||||||
|
<tr><td>autostart.c</td><td>XDG autostart support</td></tr>
|
||||||
|
<tr><td>config.c</td><td>Configuration parsing</td></tr>
|
||||||
|
<tr><td>ai.c</td><td>AI integration</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<h4>Research Sources</h4>
|
||||||
|
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
|
||||||
|
<li><a href="https://en.wikipedia.org/wiki/Modular_programming" target="_blank">Modular Programming</a> - Wikipedia</li>
|
||||||
|
<li><a href="https://en.wikipedia.org/wiki/Separation_of_concerns" target="_blank">Separation of Concerns</a> - Wikipedia</li>
|
||||||
|
<li><a href="https://thecloudstrap.com/chapter-14-modular-programming-in-c/" target="_blank">Modular Programming in C</a> - TheCloudStrap</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 id="defensive" style="margin-top: 3rem;">Defensive Programming</h2>
|
||||||
|
<div class="card" style="margin-bottom: 2rem;">
|
||||||
|
<h4>Core Principles</h4>
|
||||||
|
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
|
||||||
|
<li><strong>Null Checks</strong> - All pointer parameters validated before use</li>
|
||||||
|
<li><strong>Bounds Checking</strong> - Array indices and string lengths verified</li>
|
||||||
|
<li><strong>Return Value Checking</strong> - malloc(), fopen(), etc. checked for failure</li>
|
||||||
|
<li><strong>String Safety</strong> - strncpy() with size-1, explicit null termination</li>
|
||||||
|
<li><strong>Assertions</strong> - assert() for programmer errors in debug builds</li>
|
||||||
|
</ul>
|
||||||
|
<h4>Examples in DWN</h4>
|
||||||
|
<div class="code-header">
|
||||||
|
<span>Null Check Pattern</span>
|
||||||
|
</div>
|
||||||
|
<pre><code>const char *config_get_terminal(void) {
|
||||||
|
if (dwn != NULL && dwn->config != NULL) {
|
||||||
|
return dwn->config->terminal;
|
||||||
|
}
|
||||||
|
return "xterm"; // Safe fallback
|
||||||
|
}</code></pre>
|
||||||
|
<div class="code-header">
|
||||||
|
<span>String Safety Pattern</span>
|
||||||
|
</div>
|
||||||
|
<pre><code>strncpy(cfg->terminal, value, sizeof(cfg->terminal) - 1);
|
||||||
|
cfg->terminal[sizeof(cfg->terminal) - 1] = '\0'; // Guarantee null termination</code></pre>
|
||||||
|
<h4>Research Sources</h4>
|
||||||
|
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
|
||||||
|
<li><a href="https://wiki.sei.cmu.edu/confluence/display/c/API00-C.+Functions+should+validate+their+parameters" target="_blank">API00-C: Functions should validate their parameters</a> - SEI CERT</li>
|
||||||
|
<li><a href="https://enterprisecraftsmanship.com/posts/defensive-programming/" target="_blank">Defensive programming: the good, the bad and the ugly</a> - Enterprise Craftsmanship</li>
|
||||||
|
<li><a href="https://www.cse.psu.edu/~gxt29/teaching/cs447s19/slides/05defensiveProg.pdf" target="_blank">Defensive Programming</a> - Penn State (PDF)</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 id="sources" style="margin-top: 3rem;">Complete Research Sources</h2>
|
||||||
|
<div class="card">
|
||||||
|
<h4>C Design Patterns</h4>
|
||||||
|
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
|
||||||
|
<li><a href="https://interrupt.memfault.com/blog/opaque-pointers" target="_blank">Practical Design Patterns: Opaque Pointers and Objects in C</a> - Memfault</li>
|
||||||
|
<li><a href="https://wiki.sei.cmu.edu/confluence/display/c/" target="_blank">SEI CERT C Coding Standard</a> - Carnegie Mellon University</li>
|
||||||
|
<li><a href="https://refactoring.guru/design-patterns" target="_blank">Design Patterns Catalog</a> - Refactoring Guru</li>
|
||||||
|
<li><a href="https://sourcemaking.com/design_patterns" target="_blank">Design Patterns</a> - SourceMaking</li>
|
||||||
|
<li><a href="https://embeddedartistry.com/fieldatlas/" target="_blank">Embedded Artistry Field Atlas</a> - Embedded Artistry</li>
|
||||||
|
<li><a href="https://www.state-machine.com/doc/AN_Simple_OOP_in_C.pdf" target="_blank">Object-Oriented Programming in C</a> - Quantum Leaps</li>
|
||||||
|
</ul>
|
||||||
|
<h4>X11 and freedesktop.org</h4>
|
||||||
|
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
|
||||||
|
<li><a href="https://specifications.freedesktop.org/wm/latest/" target="_blank">Extended Window Manager Hints (EWMH)</a> - freedesktop.org</li>
|
||||||
|
<li><a href="https://specifications.freedesktop.org/xembed-spec/latest/" target="_blank">XEmbed Protocol</a> - freedesktop.org</li>
|
||||||
|
<li><a href="https://specifications.freedesktop.org/systemtray-spec/systemtray-spec-0.3.html" target="_blank">System Tray Protocol</a> - freedesktop.org</li>
|
||||||
|
<li><a href="https://specifications.freedesktop.org/notification-spec/latest/" target="_blank">Desktop Notifications</a> - freedesktop.org</li>
|
||||||
|
<li><a href="https://specifications.freedesktop.org/autostart-spec/autostart-spec-latest.html" target="_blank">Desktop Application Autostart</a> - freedesktop.org</li>
|
||||||
|
<li><a href="https://specifications.freedesktop.org/desktop-entry-spec/latest/" target="_blank">Desktop Entry Specification</a> - freedesktop.org</li>
|
||||||
|
</ul>
|
||||||
|
<h4>Libraries and APIs</h4>
|
||||||
|
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
|
||||||
|
<li><a href="https://curl.se/libcurl/c/libcurl-multi.html" target="_blank">libcurl multi interface</a> - curl.se</li>
|
||||||
|
<li><a href="https://dbus.freedesktop.org/doc/dbus-specification.html" target="_blank">D-Bus Specification</a> - freedesktop.org</li>
|
||||||
|
<li><a href="https://www.x.org/releases/current/doc/" target="_blank">X Window System Documentation</a> - X.Org</li>
|
||||||
|
</ul>
|
||||||
|
<h4>Unix Programming</h4>
|
||||||
|
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
|
||||||
|
<li><a href="https://0xjet.github.io/3OHA/2022/04/11/post.html" target="_blank">UNIX daemonization and the double fork</a> - Juan Tapiador</li>
|
||||||
|
<li><a href="https://eli.thegreenplace.net/2009/04/27/using-goto-for-error-handling-in-c" target="_blank">Using goto for error handling in C</a> - Eli Bendersky</li>
|
||||||
|
<li><a href="https://gameprogrammingpatterns.com/" target="_blank">Game Programming Patterns</a> - Robert Nystrom</li>
|
||||||
|
</ul>
|
||||||
|
<h4>Community Resources</h4>
|
||||||
|
<ul style="padding-left: 1.25rem; color: var(--text-muted);">
|
||||||
|
<li><a href="https://wiki.archlinux.org/" target="_blank">ArchWiki</a> - Comprehensive Linux documentation</li>
|
||||||
|
<li><a href="https://en.wikipedia.org/" target="_blank">Wikipedia</a> - Design pattern overviews</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
<footer>
|
||||||
|
<div class="container">
|
||||||
|
<div class="footer-grid">
|
||||||
|
<div class="footer-section">
|
||||||
|
<h4>DWN Window Manager</h4>
|
||||||
|
<p style="color: var(--text-muted);">
|
||||||
|
A modern, production-ready X11 window manager with XFCE-like
|
||||||
|
functionality and optional AI integration.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="footer-section">
|
||||||
|
<h4>Documentation</h4>
|
||||||
|
<ul>
|
||||||
|
<li><a href="documentation.html">Getting Started</a></li>
|
||||||
|
<li><a href="shortcuts.html">Keyboard Shortcuts</a></li>
|
||||||
|
<li><a href="configuration.html">Configuration</a></li>
|
||||||
|
<li><a href="architecture.html">Architecture</a></li>
|
||||||
|
<li><a href="design-patterns.html">Design Patterns</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="footer-section">
|
||||||
|
<h4>Resources</h4>
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Source Code</a></li>
|
||||||
|
<li><a href="https://retoor.molodetz.nl/retoor/dwn/issues">Issue Tracker</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="footer-section">
|
||||||
|
<h4>Author</h4>
|
||||||
|
<p style="color: var(--text-muted);">
|
||||||
|
retoor <retoor@molodetz.nl>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="footer-bottom">
|
||||||
|
<p>DWN Window Manager - MIT License</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
<script src="js/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -26,6 +26,7 @@
|
|||||||
<a href="configuration.html">Configuration</a>
|
<a href="configuration.html">Configuration</a>
|
||||||
<a href="ai-features.html">AI Features</a>
|
<a href="ai-features.html">AI Features</a>
|
||||||
<a href="architecture.html">Architecture</a>
|
<a href="architecture.html">Architecture</a>
|
||||||
|
<a href="design-patterns.html">Design Patterns</a>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
|
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
|
||||||
@ -366,6 +367,7 @@
|
|||||||
<li><a href="shortcuts.html">Keyboard Shortcuts</a></li>
|
<li><a href="shortcuts.html">Keyboard Shortcuts</a></li>
|
||||||
<li><a href="configuration.html">Configuration</a></li>
|
<li><a href="configuration.html">Configuration</a></li>
|
||||||
<li><a href="architecture.html">Architecture</a></li>
|
<li><a href="architecture.html">Architecture</a></li>
|
||||||
|
<li><a href="design-patterns.html">Design Patterns</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer-section">
|
<div class="footer-section">
|
||||||
|
|||||||
@ -26,6 +26,7 @@
|
|||||||
<a href="configuration.html">Configuration</a>
|
<a href="configuration.html">Configuration</a>
|
||||||
<a href="ai-features.html">AI Features</a>
|
<a href="ai-features.html">AI Features</a>
|
||||||
<a href="architecture.html">Architecture</a>
|
<a href="architecture.html">Architecture</a>
|
||||||
|
<a href="design-patterns.html">Design Patterns</a>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
|
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
|
||||||
@ -168,7 +169,42 @@
|
|||||||
at 80 pixels per second keeps you informed without distraction.
|
at 80 pixels per second keeps you informed without distraction.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<h3 style="margin-top: 3rem; margin-bottom: 1.5rem;">System Tray Features</h3>
|
<h3 style="margin-top: 3rem; margin-bottom: 1.5rem;">XEmbed System Tray</h3>
|
||||||
|
<div class="card" style="margin-bottom: 2rem;">
|
||||||
|
<h3>📱 External Application Icons</h3>
|
||||||
|
<p>DWN implements the freedesktop.org XEmbed System Tray protocol, allowing external applications
|
||||||
|
to dock their status icons in the panel - just like XFCE, GNOME, or KDE.</p>
|
||||||
|
<p style="margin-top: 1rem; color: var(--text-muted);">Supported applications include:</p>
|
||||||
|
<ul style="margin-top: 0.5rem; padding-left: 1.25rem; color: var(--text-muted);">
|
||||||
|
<li><strong>Telegram</strong> - Notification icons for messages</li>
|
||||||
|
<li><strong>nm-applet</strong> - NetworkManager GUI</li>
|
||||||
|
<li><strong>blueman-applet</strong> - Bluetooth manager</li>
|
||||||
|
<li><strong>pasystray</strong> - PulseAudio control</li>
|
||||||
|
<li><strong>udiskie</strong> - USB automounter</li>
|
||||||
|
<li>Any application with tray icon support</li>
|
||||||
|
</ul>
|
||||||
|
<p style="margin-top: 1rem; color: var(--text-muted);">
|
||||||
|
Simply launch any tray-enabled application and its icon will automatically appear in the panel.
|
||||||
|
Click on icons to interact - all events are forwarded to the application.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<h3 style="margin-top: 3rem; margin-bottom: 1.5rem;">XDG Autostart</h3>
|
||||||
|
<div class="card" style="margin-bottom: 2rem;">
|
||||||
|
<h3>Automatic Application Startup</h3>
|
||||||
|
<p>DWN follows the XDG Autostart specification, automatically starting system services
|
||||||
|
and tray applications - just like traditional desktop environments.</p>
|
||||||
|
<p style="margin-top: 1rem; color: var(--text-muted);">Directories scanned at startup:</p>
|
||||||
|
<ul style="margin-top: 0.5rem; padding-left: 1.25rem; color: var(--text-muted);">
|
||||||
|
<li><code>/etc/xdg/autostart/</code> - System defaults (nm-applet, blueman, power-manager)</li>
|
||||||
|
<li><code>~/.config/autostart/</code> - User XDG autostart entries</li>
|
||||||
|
<li><code>~/.config/dwn/autostart.d/</code> - DWN-specific symlinks and scripts</li>
|
||||||
|
</ul>
|
||||||
|
<p style="margin-top: 1rem; color: var(--text-muted);">
|
||||||
|
All applications launch concurrently for fastest boot time. Properly handles .desktop
|
||||||
|
file fields including Hidden, TryExec, OnlyShowIn, and NotShowIn.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<h3 style="margin-top: 2rem; margin-bottom: 1.5rem;">Built-in Widgets</h3>
|
||||||
<div class="features-grid">
|
<div class="features-grid">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h3>🔋 Battery Monitor</h3>
|
<h3>🔋 Battery Monitor</h3>
|
||||||
@ -336,6 +372,37 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
<section class="section">
|
||||||
|
<div class="container">
|
||||||
|
<h2>Learning DWN</h2>
|
||||||
|
<p style="color: var(--text-muted); max-width: 700px; margin-bottom: 2rem;">
|
||||||
|
Two built-in modes help you learn DWN quickly: an interactive tutorial and
|
||||||
|
an automated demo that showcases all features.
|
||||||
|
</p>
|
||||||
|
<div class="features-grid" style="grid-template-columns: repeat(2, 1fr);">
|
||||||
|
<div class="card">
|
||||||
|
<h3><span class="card-icon">📚</span> Interactive Tutorial</h3>
|
||||||
|
<p>Press <kbd>Super</kbd> + <kbd>T</kbd> to start a hands-on tutorial that:</p>
|
||||||
|
<ul style="margin-top: 1rem; padding-left: 1.25rem; color: var(--text-muted);">
|
||||||
|
<li>Guides you through essential shortcuts step-by-step</li>
|
||||||
|
<li>Waits for you to press the correct key combination</li>
|
||||||
|
<li>Automatically advances when you complete each step</li>
|
||||||
|
<li>Can be restarted at any time</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h3><span class="card-icon">🎬</span> Demo Mode</h3>
|
||||||
|
<p>Press <kbd>Super</kbd> + <kbd>Shift</kbd> + <kbd>D</kbd> for an automated showcase:</p>
|
||||||
|
<ul style="margin-top: 1rem; padding-left: 1.25rem; color: var(--text-muted);">
|
||||||
|
<li>Demonstrates window management, workspaces, and layouts</li>
|
||||||
|
<li>Shows panel features and system tray</li>
|
||||||
|
<li>Highlights AI integration and news ticker</li>
|
||||||
|
<li>Displays complete keyboard shortcut reference</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
<section class="section section-alt">
|
<section class="section section-alt">
|
||||||
<div class="container" style="text-align: center;">
|
<div class="container" style="text-align: center;">
|
||||||
<h2>Ready to Try DWN?</h2>
|
<h2>Ready to Try DWN?</h2>
|
||||||
@ -366,6 +433,7 @@
|
|||||||
<li><a href="shortcuts.html">Keyboard Shortcuts</a></li>
|
<li><a href="shortcuts.html">Keyboard Shortcuts</a></li>
|
||||||
<li><a href="configuration.html">Configuration</a></li>
|
<li><a href="configuration.html">Configuration</a></li>
|
||||||
<li><a href="architecture.html">Architecture</a></li>
|
<li><a href="architecture.html">Architecture</a></li>
|
||||||
|
<li><a href="design-patterns.html">Design Patterns</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer-section">
|
<div class="footer-section">
|
||||||
|
|||||||
@ -27,6 +27,7 @@
|
|||||||
<a href="configuration.html">Configuration</a>
|
<a href="configuration.html">Configuration</a>
|
||||||
<a href="ai-features.html">AI Features</a>
|
<a href="ai-features.html">AI Features</a>
|
||||||
<a href="architecture.html">Architecture</a>
|
<a href="architecture.html">Architecture</a>
|
||||||
|
<a href="design-patterns.html">Design Patterns</a>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
|
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
|
||||||
@ -93,8 +94,14 @@
|
|||||||
<div class="feature-card">
|
<div class="feature-card">
|
||||||
<div class="feature-icon">💻</div>
|
<div class="feature-icon">💻</div>
|
||||||
<h3>System Tray</h3>
|
<h3>System Tray</h3>
|
||||||
<p>Integrated system tray with battery, volume, and WiFi indicators.
|
<p>XEmbed protocol for external app icons (Telegram, Bluetooth, etc.) plus
|
||||||
Volume slider and network selection dropdowns included.</p>
|
built-in battery, volume, and WiFi widgets with interactive controls.</p>
|
||||||
|
</div>
|
||||||
|
<div class="feature-card">
|
||||||
|
<div class="feature-icon">⚡</div>
|
||||||
|
<h3>XDG Autostart</h3>
|
||||||
|
<p>Automatic startup of system services and tray applications following the
|
||||||
|
XDG Autostart spec. Works with nm-applet, blueman, and more.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -249,6 +256,7 @@ echo "exec dwn" >> ~/.xinitrc</code></pre>
|
|||||||
<li><a href="shortcuts.html">Keyboard Shortcuts</a></li>
|
<li><a href="shortcuts.html">Keyboard Shortcuts</a></li>
|
||||||
<li><a href="configuration.html">Configuration</a></li>
|
<li><a href="configuration.html">Configuration</a></li>
|
||||||
<li><a href="architecture.html">Architecture</a></li>
|
<li><a href="architecture.html">Architecture</a></li>
|
||||||
|
<li><a href="design-patterns.html">Design Patterns</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer-section">
|
<div class="footer-section">
|
||||||
|
|||||||
@ -26,6 +26,7 @@
|
|||||||
<a href="configuration.html">Configuration</a>
|
<a href="configuration.html">Configuration</a>
|
||||||
<a href="ai-features.html">AI Features</a>
|
<a href="ai-features.html">AI Features</a>
|
||||||
<a href="architecture.html">Architecture</a>
|
<a href="architecture.html">Architecture</a>
|
||||||
|
<a href="design-patterns.html">Design Patterns</a>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
|
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
|
||||||
@ -528,6 +529,7 @@ sudo dnf install dejavu-fonts-all liberation-fonts # Fedora</code></pre>
|
|||||||
<li><a href="shortcuts.html">Keyboard Shortcuts</a></li>
|
<li><a href="shortcuts.html">Keyboard Shortcuts</a></li>
|
||||||
<li><a href="configuration.html">Configuration</a></li>
|
<li><a href="configuration.html">Configuration</a></li>
|
||||||
<li><a href="architecture.html">Architecture</a></li>
|
<li><a href="architecture.html">Architecture</a></li>
|
||||||
|
<li><a href="design-patterns.html">Design Patterns</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer-section">
|
<div class="footer-section">
|
||||||
|
|||||||
@ -26,6 +26,7 @@
|
|||||||
<a href="configuration.html">Configuration</a>
|
<a href="configuration.html">Configuration</a>
|
||||||
<a href="ai-features.html">AI Features</a>
|
<a href="ai-features.html">AI Features</a>
|
||||||
<a href="architecture.html">Architecture</a>
|
<a href="architecture.html">Architecture</a>
|
||||||
|
<a href="design-patterns.html">Design Patterns</a>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
|
<li><a href="https://retoor.molodetz.nl/retoor/dwn">Git</a></li>
|
||||||
@ -107,6 +108,10 @@
|
|||||||
<td><kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>Tab</kbd></td>
|
<td><kbd>Alt</kbd> + <kbd>Shift</kbd> + <kbd>Tab</kbd></td>
|
||||||
<td>Cycle to previous window</td>
|
<td>Cycle to previous window</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>Alt</kbd> + <kbd>F9</kbd></td>
|
||||||
|
<td>Toggle minimize/restore</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><kbd>Alt</kbd> + <kbd>F10</kbd></td>
|
<td><kbd>Alt</kbd> + <kbd>F10</kbd></td>
|
||||||
<td>Toggle maximize</td>
|
<td>Toggle maximize</td>
|
||||||
@ -245,6 +250,14 @@
|
|||||||
<td><kbd>Super</kbd> + <kbd>D</kbd></td>
|
<td><kbd>Super</kbd> + <kbd>D</kbd></td>
|
||||||
<td>Decrease master window count</td>
|
<td>Decrease master window count</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>Super</kbd> + <kbd>Left</kbd></td>
|
||||||
|
<td>Snap window to left half (50% width)</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>Super</kbd> + <kbd>Right</kbd></td>
|
||||||
|
<td>Snap window to right half (50% width)</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@ -322,6 +335,10 @@
|
|||||||
<td><kbd>Super</kbd> + <kbd>T</kbd></td>
|
<td><kbd>Super</kbd> + <kbd>T</kbd></td>
|
||||||
<td>Start/continue interactive tutorial</td>
|
<td>Start/continue interactive tutorial</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><kbd>Super</kbd> + <kbd>Shift</kbd> + <kbd>D</kbd></td>
|
||||||
|
<td>Start/stop demo mode</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><kbd>Super</kbd> + <kbd>Backspace</kbd></td>
|
<td><kbd>Super</kbd> + <kbd>Backspace</kbd></td>
|
||||||
<td>Quit DWN</td>
|
<td>Quit DWN</td>
|
||||||
@ -363,10 +380,10 @@
|
|||||||
<kbd>Super</kbd>+<kbd>H</kbd>/<kbd>L</kbd> Resize master
|
<kbd>Super</kbd>+<kbd>H</kbd>/<kbd>L</kbd> Resize master
|
||||||
</li>
|
</li>
|
||||||
<li style="padding: 0.5rem 0; border-bottom: 1px solid var(--border-color);">
|
<li style="padding: 0.5rem 0; border-bottom: 1px solid var(--border-color);">
|
||||||
<kbd>Shift</kbd>+<kbd>F1-9</kbd> Move to workspace
|
<kbd>Super</kbd>+<kbd>Left</kbd>/<kbd>Right</kbd> Snap 50%
|
||||||
</li>
|
</li>
|
||||||
<li style="padding: 0.5rem 0; border-bottom: 1px solid var(--border-color);">
|
<li style="padding: 0.5rem 0; border-bottom: 1px solid var(--border-color);">
|
||||||
<kbd>Super</kbd>+<kbd>Shift</kbd>+<kbd>A</kbd> AI command
|
<kbd>Shift</kbd>+<kbd>F1-9</kbd> Move to workspace
|
||||||
</li>
|
</li>
|
||||||
<li style="padding: 0.5rem 0;">
|
<li style="padding: 0.5rem 0;">
|
||||||
<kbd>Super</kbd>+<kbd>S</kbd> Show shortcuts
|
<kbd>Super</kbd>+<kbd>S</kbd> Show shortcuts
|
||||||
@ -395,6 +412,7 @@
|
|||||||
<li><a href="shortcuts.html">Keyboard Shortcuts</a></li>
|
<li><a href="shortcuts.html">Keyboard Shortcuts</a></li>
|
||||||
<li><a href="configuration.html">Configuration</a></li>
|
<li><a href="configuration.html">Configuration</a></li>
|
||||||
<li><a href="architecture.html">Architecture</a></li>
|
<li><a href="architecture.html">Architecture</a></li>
|
||||||
|
<li><a href="design-patterns.html">Design Patterns</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer-section">
|
<div class="footer-section">
|
||||||
|
|||||||
74
src/ai.c
74
src/ai.c
@ -16,12 +16,17 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <stdatomic.h>
|
||||||
|
|
||||||
#define OPENROUTER_URL "https://openrouter.ai/api/v1/chat/completions"
|
#define OPENROUTER_URL "https://openrouter.ai/api/v1/chat/completions"
|
||||||
|
|
||||||
static AIRequest *request_queue = NULL;
|
static AIRequest *request_queue = NULL;
|
||||||
static CURLM *curl_multi = NULL;
|
static CURLM *curl_multi = NULL;
|
||||||
static AIContext current_context;
|
static AIContext current_context;
|
||||||
|
static pthread_mutex_t curl_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
static pthread_mutex_t queue_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
static atomic_int ai_shutting_down = 0;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char *data;
|
char *data;
|
||||||
@ -54,6 +59,8 @@ bool ai_init(void)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
atomic_store(&ai_shutting_down, 0);
|
||||||
|
|
||||||
if (dwn->config->openrouter_api_key[0] == '\0') {
|
if (dwn->config->openrouter_api_key[0] == '\0') {
|
||||||
LOG_INFO("AI features disabled (no OPENROUTER_API_KEY)");
|
LOG_INFO("AI features disabled (no OPENROUTER_API_KEY)");
|
||||||
dwn->ai_enabled = false;
|
dwn->ai_enabled = false;
|
||||||
@ -61,12 +68,15 @@ bool ai_init(void)
|
|||||||
}
|
}
|
||||||
|
|
||||||
curl_global_init(CURL_GLOBAL_DEFAULT);
|
curl_global_init(CURL_GLOBAL_DEFAULT);
|
||||||
curl_multi = curl_multi_init();
|
|
||||||
|
|
||||||
|
pthread_mutex_lock(&curl_mutex);
|
||||||
|
curl_multi = curl_multi_init();
|
||||||
if (curl_multi == NULL) {
|
if (curl_multi == NULL) {
|
||||||
|
pthread_mutex_unlock(&curl_mutex);
|
||||||
LOG_ERROR("Failed to initialize curl multi handle");
|
LOG_ERROR("Failed to initialize curl multi handle");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
pthread_mutex_unlock(&curl_mutex);
|
||||||
|
|
||||||
dwn->ai_enabled = true;
|
dwn->ai_enabled = true;
|
||||||
LOG_INFO("AI features enabled");
|
LOG_INFO("AI features enabled");
|
||||||
@ -76,6 +86,9 @@ bool ai_init(void)
|
|||||||
|
|
||||||
void ai_cleanup(void)
|
void ai_cleanup(void)
|
||||||
{
|
{
|
||||||
|
atomic_store(&ai_shutting_down, 1);
|
||||||
|
|
||||||
|
pthread_mutex_lock(&queue_mutex);
|
||||||
while (request_queue != NULL) {
|
while (request_queue != NULL) {
|
||||||
AIRequest *next = request_queue->next;
|
AIRequest *next = request_queue->next;
|
||||||
if (request_queue->prompt) free(request_queue->prompt);
|
if (request_queue->prompt) free(request_queue->prompt);
|
||||||
@ -83,11 +96,14 @@ void ai_cleanup(void)
|
|||||||
free(request_queue);
|
free(request_queue);
|
||||||
request_queue = next;
|
request_queue = next;
|
||||||
}
|
}
|
||||||
|
pthread_mutex_unlock(&queue_mutex);
|
||||||
|
|
||||||
|
pthread_mutex_lock(&curl_mutex);
|
||||||
if (curl_multi != NULL) {
|
if (curl_multi != NULL) {
|
||||||
curl_multi_cleanup(curl_multi);
|
curl_multi_cleanup(curl_multi);
|
||||||
curl_multi = NULL;
|
curl_multi = NULL;
|
||||||
}
|
}
|
||||||
|
pthread_mutex_unlock(&curl_mutex);
|
||||||
|
|
||||||
curl_global_cleanup();
|
curl_global_cleanup();
|
||||||
}
|
}
|
||||||
@ -104,6 +120,10 @@ AIRequest *ai_send_request(const char *prompt, void (*callback)(AIRequest *))
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (atomic_load(&ai_shutting_down)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
AIRequest *req = dwn_calloc(1, sizeof(AIRequest));
|
AIRequest *req = dwn_calloc(1, sizeof(AIRequest));
|
||||||
req->prompt = dwn_strdup(prompt);
|
req->prompt = dwn_strdup(prompt);
|
||||||
req->state = AI_STATE_PENDING;
|
req->state = AI_STATE_PENDING;
|
||||||
@ -162,16 +182,21 @@ AIRequest *ai_send_request(const char *prompt, void (*callback)(AIRequest *))
|
|||||||
curl_easy_setopt(easy, CURLOPT_SSL_VERIFYPEER, 1L);
|
curl_easy_setopt(easy, CURLOPT_SSL_VERIFYPEER, 1L);
|
||||||
curl_easy_setopt(easy, CURLOPT_SSL_VERIFYHOST, 2L);
|
curl_easy_setopt(easy, CURLOPT_SSL_VERIFYHOST, 2L);
|
||||||
|
|
||||||
curl_multi_add_handle(curl_multi, easy);
|
pthread_mutex_lock(&curl_mutex);
|
||||||
|
if (curl_multi != NULL) {
|
||||||
|
curl_multi_add_handle(curl_multi, easy);
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&curl_mutex);
|
||||||
|
|
||||||
|
pthread_mutex_lock(&queue_mutex);
|
||||||
req->next = request_queue;
|
req->next = request_queue;
|
||||||
request_queue = req;
|
request_queue = req;
|
||||||
|
pthread_mutex_unlock(&queue_mutex);
|
||||||
|
|
||||||
req->user_data = response;
|
req->user_data = response;
|
||||||
|
|
||||||
LOG_DEBUG("AI request sent: %.50s...", prompt);
|
LOG_DEBUG("AI request sent: %.50s...", prompt);
|
||||||
|
|
||||||
|
|
||||||
return req;
|
return req;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,6 +206,7 @@ void ai_cancel_request(AIRequest *req)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pthread_mutex_lock(&queue_mutex);
|
||||||
AIRequest **pp = &request_queue;
|
AIRequest **pp = &request_queue;
|
||||||
while (*pp != NULL) {
|
while (*pp != NULL) {
|
||||||
if (*pp == req) {
|
if (*pp == req) {
|
||||||
@ -189,6 +215,7 @@ void ai_cancel_request(AIRequest *req)
|
|||||||
}
|
}
|
||||||
pp = &(*pp)->next;
|
pp = &(*pp)->next;
|
||||||
}
|
}
|
||||||
|
pthread_mutex_unlock(&queue_mutex);
|
||||||
|
|
||||||
req->state = AI_STATE_ERROR;
|
req->state = AI_STATE_ERROR;
|
||||||
|
|
||||||
@ -204,7 +231,13 @@ void ai_cancel_request(AIRequest *req)
|
|||||||
|
|
||||||
void ai_process_pending(void)
|
void ai_process_pending(void)
|
||||||
{
|
{
|
||||||
|
if (atomic_load(&ai_shutting_down)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_lock(&curl_mutex);
|
||||||
if (curl_multi == NULL) {
|
if (curl_multi == NULL) {
|
||||||
|
pthread_mutex_unlock(&curl_mutex);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -277,6 +310,7 @@ void ai_process_pending(void)
|
|||||||
curl_easy_cleanup(easy);
|
curl_easy_cleanup(easy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pthread_mutex_unlock(&curl_mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -586,6 +620,10 @@ ExaRequest *exa_search(const char *query, void (*callback)(ExaRequest *))
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (atomic_load(&ai_shutting_down)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
ExaRequest *req = dwn_calloc(1, sizeof(ExaRequest));
|
ExaRequest *req = dwn_calloc(1, sizeof(ExaRequest));
|
||||||
req->query = dwn_strdup(query);
|
req->query = dwn_strdup(query);
|
||||||
req->state = AI_STATE_PENDING;
|
req->state = AI_STATE_PENDING;
|
||||||
@ -638,13 +676,20 @@ ExaRequest *exa_search(const char *query, void (*callback)(ExaRequest *))
|
|||||||
curl_easy_setopt(easy, CURLOPT_SSL_VERIFYPEER, 1L);
|
curl_easy_setopt(easy, CURLOPT_SSL_VERIFYPEER, 1L);
|
||||||
curl_easy_setopt(easy, CURLOPT_SSL_VERIFYHOST, 2L);
|
curl_easy_setopt(easy, CURLOPT_SSL_VERIFYHOST, 2L);
|
||||||
|
|
||||||
|
pthread_mutex_lock(&curl_mutex);
|
||||||
if (curl_multi == NULL) {
|
if (curl_multi == NULL) {
|
||||||
curl_multi = curl_multi_init();
|
curl_multi = curl_multi_init();
|
||||||
}
|
}
|
||||||
curl_multi_add_handle(curl_multi, easy);
|
if (curl_multi != NULL) {
|
||||||
|
curl_multi_add_handle(curl_multi, easy);
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&curl_mutex);
|
||||||
|
|
||||||
|
pthread_mutex_lock(&queue_mutex);
|
||||||
req->next = exa_queue;
|
req->next = exa_queue;
|
||||||
exa_queue = req;
|
exa_queue = req;
|
||||||
|
pthread_mutex_unlock(&queue_mutex);
|
||||||
|
|
||||||
req->user_data = response;
|
req->user_data = response;
|
||||||
|
|
||||||
LOG_DEBUG("Exa search sent: %s", query);
|
LOG_DEBUG("Exa search sent: %s", query);
|
||||||
@ -654,10 +699,24 @@ ExaRequest *exa_search(const char *query, void (*callback)(ExaRequest *))
|
|||||||
|
|
||||||
void exa_process_pending(void)
|
void exa_process_pending(void)
|
||||||
{
|
{
|
||||||
if (curl_multi == NULL || exa_queue == NULL) {
|
if (atomic_load(&ai_shutting_down)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pthread_mutex_lock(&curl_mutex);
|
||||||
|
if (curl_multi == NULL) {
|
||||||
|
pthread_mutex_unlock(&curl_mutex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_lock(&queue_mutex);
|
||||||
|
if (exa_queue == NULL) {
|
||||||
|
pthread_mutex_unlock(&queue_mutex);
|
||||||
|
pthread_mutex_unlock(&curl_mutex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&queue_mutex);
|
||||||
|
|
||||||
int running_handles;
|
int running_handles;
|
||||||
curl_multi_perform(curl_multi, &running_handles);
|
curl_multi_perform(curl_multi, &running_handles);
|
||||||
|
|
||||||
@ -671,6 +730,7 @@ void exa_process_pending(void)
|
|||||||
|
|
||||||
curl_easy_getinfo(easy, CURLINFO_PRIVATE, &req);
|
curl_easy_getinfo(easy, CURLINFO_PRIVATE, &req);
|
||||||
|
|
||||||
|
pthread_mutex_lock(&queue_mutex);
|
||||||
bool is_exa = false;
|
bool is_exa = false;
|
||||||
for (ExaRequest *r = exa_queue; r != NULL; r = r->next) {
|
for (ExaRequest *r = exa_queue; r != NULL; r = r->next) {
|
||||||
if (r == req) {
|
if (r == req) {
|
||||||
@ -678,6 +738,7 @@ void exa_process_pending(void)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pthread_mutex_unlock(&queue_mutex);
|
||||||
|
|
||||||
if (is_exa && req != NULL) {
|
if (is_exa && req != NULL) {
|
||||||
ResponseBuffer *buf = (ResponseBuffer *)req->user_data;
|
ResponseBuffer *buf = (ResponseBuffer *)req->user_data;
|
||||||
@ -699,6 +760,7 @@ void exa_process_pending(void)
|
|||||||
dwn_free(buf);
|
dwn_free(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pthread_mutex_lock(&queue_mutex);
|
||||||
ExaRequest **pp = &exa_queue;
|
ExaRequest **pp = &exa_queue;
|
||||||
while (*pp != NULL) {
|
while (*pp != NULL) {
|
||||||
if (*pp == req) {
|
if (*pp == req) {
|
||||||
@ -707,12 +769,14 @@ void exa_process_pending(void)
|
|||||||
}
|
}
|
||||||
pp = &(*pp)->next;
|
pp = &(*pp)->next;
|
||||||
}
|
}
|
||||||
|
pthread_mutex_unlock(&queue_mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
curl_multi_remove_handle(curl_multi, easy);
|
curl_multi_remove_handle(curl_multi, easy);
|
||||||
curl_easy_cleanup(easy);
|
curl_easy_cleanup(easy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pthread_mutex_unlock(&curl_mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void exa_launcher_callback(ExaRequest *req)
|
static void exa_launcher_callback(ExaRequest *req)
|
||||||
|
|||||||
73
src/atoms.c
73
src/atoms.c
@ -87,6 +87,8 @@ void atoms_init(Display *display)
|
|||||||
|
|
||||||
ewmh.NET_SYSTEM_TRAY_OPCODE = ATOM("_NET_SYSTEM_TRAY_OPCODE");
|
ewmh.NET_SYSTEM_TRAY_OPCODE = ATOM("_NET_SYSTEM_TRAY_OPCODE");
|
||||||
ewmh.NET_SYSTEM_TRAY_S0 = ATOM("_NET_SYSTEM_TRAY_S0");
|
ewmh.NET_SYSTEM_TRAY_S0 = ATOM("_NET_SYSTEM_TRAY_S0");
|
||||||
|
ewmh.NET_SYSTEM_TRAY_ORIENTATION = ATOM("_NET_SYSTEM_TRAY_ORIENTATION");
|
||||||
|
ewmh.NET_SYSTEM_TRAY_VISUAL = ATOM("_NET_SYSTEM_TRAY_VISUAL");
|
||||||
ewmh.MANAGER = ATOM("MANAGER");
|
ewmh.MANAGER = ATOM("MANAGER");
|
||||||
ewmh.XEMBED = ATOM("_XEMBED");
|
ewmh.XEMBED = ATOM("_XEMBED");
|
||||||
ewmh.XEMBED_INFO = ATOM("_XEMBED_INFO");
|
ewmh.XEMBED_INFO = ATOM("_XEMBED_INFO");
|
||||||
@ -437,3 +439,74 @@ void atoms_send_client_message(Window window, Atom message_type,
|
|||||||
XSendEvent(dwn->display, dwn->root, False,
|
XSendEvent(dwn->display, dwn->root, False,
|
||||||
SubstructureNotifyMask | SubstructureRedirectMask, &ev);
|
SubstructureNotifyMask | SubstructureRedirectMask, &ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool atoms_update_wm_state(Window window, Atom state, bool add)
|
||||||
|
{
|
||||||
|
if (dwn == NULL || dwn->display == NULL || window == None) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Atom actual_type;
|
||||||
|
int actual_format;
|
||||||
|
unsigned long nitems, bytes_after;
|
||||||
|
unsigned char *data = NULL;
|
||||||
|
Atom *states = NULL;
|
||||||
|
int state_count = 0;
|
||||||
|
|
||||||
|
if (XGetWindowProperty(dwn->display, window, ewmh.NET_WM_STATE,
|
||||||
|
0, 32, False, XA_ATOM,
|
||||||
|
&actual_type, &actual_format, &nitems, &bytes_after,
|
||||||
|
&data) == Success && data != NULL) {
|
||||||
|
states = (Atom *)data;
|
||||||
|
state_count = (int)nitems;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool found = false;
|
||||||
|
int found_index = -1;
|
||||||
|
for (int i = 0; i < state_count; i++) {
|
||||||
|
if (states[i] == state) {
|
||||||
|
found = true;
|
||||||
|
found_index = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (add && found) {
|
||||||
|
if (data) XFree(data);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!add && !found) {
|
||||||
|
if (data) XFree(data);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Atom new_states[32];
|
||||||
|
int new_count = 0;
|
||||||
|
|
||||||
|
if (add) {
|
||||||
|
for (int i = 0; i < state_count && new_count < 31; i++) {
|
||||||
|
new_states[new_count++] = states[i];
|
||||||
|
}
|
||||||
|
if (new_count < 32) {
|
||||||
|
new_states[new_count++] = state;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (int i = 0; i < state_count && new_count < 32; i++) {
|
||||||
|
if (i != found_index) {
|
||||||
|
new_states[new_count++] = states[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data) XFree(data);
|
||||||
|
|
||||||
|
if (new_count > 0) {
|
||||||
|
XChangeProperty(dwn->display, window, ewmh.NET_WM_STATE,
|
||||||
|
XA_ATOM, 32, PropModeReplace,
|
||||||
|
(unsigned char *)new_states, new_count);
|
||||||
|
} else {
|
||||||
|
XDeleteProperty(dwn->display, window, ewmh.NET_WM_STATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|||||||
290
src/autostart.c
Normal file
290
src/autostart.c
Normal file
@ -0,0 +1,290 @@
|
|||||||
|
/*
|
||||||
|
* DWN - Desktop Window Manager
|
||||||
|
* retoor <retoor@molodetz.nl>
|
||||||
|
* XDG Autostart support
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "autostart.h"
|
||||||
|
#include "dwn.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
static void scan_xdg_directory(const char *dir_path);
|
||||||
|
static void scan_symlink_directory(const char *dir_path);
|
||||||
|
static int parse_desktop_file(const char *path, char *exec_cmd, size_t exec_size);
|
||||||
|
static void strip_exec_field_codes(char *exec);
|
||||||
|
|
||||||
|
void autostart_init(void)
|
||||||
|
{
|
||||||
|
if (dwn == NULL || dwn->config == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dwn->config->autostart_enabled) {
|
||||||
|
LOG_DEBUG("Autostart disabled in config");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dwn->config->autostart_path[0] != '\0') {
|
||||||
|
struct stat st;
|
||||||
|
if (stat(dwn->config->autostart_path, &st) != 0) {
|
||||||
|
if (mkdir(dwn->config->autostart_path, 0755) == 0) {
|
||||||
|
LOG_INFO("Created autostart directory: %s", dwn->config->autostart_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_INFO("Autostart initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
void autostart_run(void)
|
||||||
|
{
|
||||||
|
if (dwn == NULL || dwn->config == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dwn->config->autostart_enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_INFO("Running autostart applications");
|
||||||
|
|
||||||
|
if (dwn->config->autostart_xdg) {
|
||||||
|
scan_xdg_directory("/etc/xdg/autostart");
|
||||||
|
|
||||||
|
char *user_autostart = expand_path("~/.config/autostart");
|
||||||
|
if (user_autostart != NULL) {
|
||||||
|
scan_xdg_directory(user_autostart);
|
||||||
|
free(user_autostart);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dwn->config->autostart_path[0] != '\0') {
|
||||||
|
scan_symlink_directory(dwn->config->autostart_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_INFO("Autostart complete");
|
||||||
|
}
|
||||||
|
|
||||||
|
void autostart_cleanup(void)
|
||||||
|
{
|
||||||
|
LOG_DEBUG("Autostart cleanup");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void scan_xdg_directory(const char *dir_path)
|
||||||
|
{
|
||||||
|
if (dir_path == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DIR *dir = opendir(dir_path);
|
||||||
|
if (dir == NULL) {
|
||||||
|
LOG_DEBUG("Cannot open autostart directory: %s", dir_path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DEBUG("Scanning XDG autostart directory: %s", dir_path);
|
||||||
|
|
||||||
|
struct dirent *entry;
|
||||||
|
while ((entry = readdir(dir)) != NULL) {
|
||||||
|
if (entry->d_name[0] == '.') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!str_ends_with(entry->d_name, ".desktop")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
char full_path[PATH_MAX];
|
||||||
|
int ret = snprintf(full_path, sizeof(full_path), "%s/%s", dir_path, entry->d_name);
|
||||||
|
if (ret < 0 || (size_t)ret >= sizeof(full_path)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
char exec_cmd[512];
|
||||||
|
if (parse_desktop_file(full_path, exec_cmd, sizeof(exec_cmd)) == 0) {
|
||||||
|
LOG_INFO("Autostart: %s -> %s", entry->d_name, exec_cmd);
|
||||||
|
spawn_async(exec_cmd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
closedir(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void scan_symlink_directory(const char *dir_path)
|
||||||
|
{
|
||||||
|
if (dir_path == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DIR *dir = opendir(dir_path);
|
||||||
|
if (dir == NULL) {
|
||||||
|
LOG_DEBUG("Cannot open symlink directory: %s", dir_path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DEBUG("Scanning symlink directory: %s", dir_path);
|
||||||
|
|
||||||
|
struct dirent *entry;
|
||||||
|
while ((entry = readdir(dir)) != NULL) {
|
||||||
|
if (entry->d_name[0] == '.') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
char full_path[PATH_MAX];
|
||||||
|
int ret = snprintf(full_path, sizeof(full_path), "%s/%s", dir_path, entry->d_name);
|
||||||
|
if (ret < 0 || (size_t)ret >= sizeof(full_path)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (access(full_path, X_OK) != 0) {
|
||||||
|
LOG_DEBUG("Skipping non-executable: %s", entry->d_name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_INFO("Autostart (symlink): %s", entry->d_name);
|
||||||
|
spawn_async(full_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
closedir(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int parse_desktop_file(const char *path, char *exec_cmd, size_t exec_size)
|
||||||
|
{
|
||||||
|
if (path == NULL || exec_cmd == NULL || exec_size == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE *f = fopen(path, "r");
|
||||||
|
if (f == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
char line[1024];
|
||||||
|
int in_desktop_entry = 0;
|
||||||
|
int hidden = 0;
|
||||||
|
int enabled = 1;
|
||||||
|
char try_exec[256] = {0};
|
||||||
|
exec_cmd[0] = '\0';
|
||||||
|
|
||||||
|
while (fgets(line, sizeof(line), f) != NULL) {
|
||||||
|
size_t len = strlen(line);
|
||||||
|
while (len > 0 && (line[len-1] == '\n' || line[len-1] == '\r')) {
|
||||||
|
line[--len] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
char *trimmed = str_trim(line);
|
||||||
|
if (trimmed[0] == '\0' || trimmed[0] == '#') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(trimmed, "[Desktop Entry]") == 0) {
|
||||||
|
in_desktop_entry = 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trimmed[0] == '[') {
|
||||||
|
in_desktop_entry = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!in_desktop_entry) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(trimmed, "Hidden=true") == 0) {
|
||||||
|
hidden = 1;
|
||||||
|
} else if (strcmp(trimmed, "X-GNOME-Autostart-enabled=false") == 0) {
|
||||||
|
enabled = 0;
|
||||||
|
} else if (strncmp(trimmed, "TryExec=", 8) == 0) {
|
||||||
|
strncpy(try_exec, trimmed + 8, sizeof(try_exec) - 1);
|
||||||
|
try_exec[sizeof(try_exec) - 1] = '\0';
|
||||||
|
} else if (strncmp(trimmed, "Exec=", 5) == 0) {
|
||||||
|
strncpy(exec_cmd, trimmed + 5, exec_size - 1);
|
||||||
|
exec_cmd[exec_size - 1] = '\0';
|
||||||
|
} else if (strncmp(trimmed, "NotShowIn=", 10) == 0) {
|
||||||
|
if (strstr(trimmed + 10, "DWN") != NULL) {
|
||||||
|
hidden = 1;
|
||||||
|
}
|
||||||
|
} else if (strncmp(trimmed, "OnlyShowIn=", 11) == 0) {
|
||||||
|
if (strstr(trimmed + 11, "DWN") == NULL) {
|
||||||
|
hidden = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
if (hidden || !enabled || exec_cmd[0] == '\0') {
|
||||||
|
exec_cmd[0] = '\0';
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (try_exec[0] != '\0') {
|
||||||
|
char *space = strchr(try_exec, ' ');
|
||||||
|
if (space != NULL) {
|
||||||
|
*space = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (access(try_exec, X_OK) != 0) {
|
||||||
|
char which_cmd[512];
|
||||||
|
snprintf(which_cmd, sizeof(which_cmd), "which %s >/dev/null 2>&1", try_exec);
|
||||||
|
if (system(which_cmd) != 0) {
|
||||||
|
exec_cmd[0] = '\0';
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
strip_exec_field_codes(exec_cmd);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void strip_exec_field_codes(char *exec)
|
||||||
|
{
|
||||||
|
if (exec == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char result[512];
|
||||||
|
size_t j = 0;
|
||||||
|
|
||||||
|
for (size_t i = 0; exec[i] != '\0' && j < sizeof(result) - 1; i++) {
|
||||||
|
if (exec[i] == '%' && exec[i+1] != '\0') {
|
||||||
|
char code = exec[i+1];
|
||||||
|
if (code == 'f' || code == 'F' || code == 'u' || code == 'U' ||
|
||||||
|
code == 'd' || code == 'D' || code == 'n' || code == 'N' ||
|
||||||
|
code == 'i' || code == 'c' || code == 'k' || code == 'v' ||
|
||||||
|
code == 'm') {
|
||||||
|
i++;
|
||||||
|
if (exec[i+1] == ' ') {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result[j++] = exec[i];
|
||||||
|
}
|
||||||
|
result[j] = '\0';
|
||||||
|
|
||||||
|
while (j > 0 && (result[j-1] == ' ' || result[j-1] == '\t')) {
|
||||||
|
result[--j] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t result_len = strlen(result);
|
||||||
|
if (result_len >= 512) {
|
||||||
|
result_len = 511;
|
||||||
|
}
|
||||||
|
memcpy(exec, result, result_len);
|
||||||
|
exec[result_len] = '\0';
|
||||||
|
}
|
||||||
13
src/client.c
13
src/client.c
@ -66,11 +66,8 @@ Client *client_create(Window window)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int target_width = (work_width * 75) / 100;
|
client->width = orig_width;
|
||||||
int target_height = (work_height * 75) / 100;
|
client->height = orig_height;
|
||||||
|
|
||||||
client->width = (orig_width > target_width) ? orig_width : target_width;
|
|
||||||
client->height = (orig_height > target_height) ? orig_height : target_height;
|
|
||||||
|
|
||||||
if (client->width > work_width - 20) client->width = work_width - 20;
|
if (client->width > work_width - 20) client->width = work_width - 20;
|
||||||
if (client->height > work_height - 20) client->height = work_height - 20;
|
if (client->height > work_height - 20) client->height = work_height - 20;
|
||||||
@ -603,6 +600,7 @@ void client_set_fullscreen(Client *client, bool fullscreen)
|
|||||||
}
|
}
|
||||||
|
|
||||||
client->flags |= CLIENT_FULLSCREEN;
|
client->flags |= CLIENT_FULLSCREEN;
|
||||||
|
atoms_update_wm_state(client->window, ewmh.NET_WM_STATE_FULLSCREEN, true);
|
||||||
|
|
||||||
client->x = 0;
|
client->x = 0;
|
||||||
client->y = 0;
|
client->y = 0;
|
||||||
@ -619,8 +617,10 @@ void client_set_fullscreen(Client *client, bool fullscreen)
|
|||||||
0, 0, client->width, client->height);
|
0, 0, client->width, client->height);
|
||||||
XMapWindow(dwn->display, client->window);
|
XMapWindow(dwn->display, client->window);
|
||||||
XRaiseWindow(dwn->display, client->window);
|
XRaiseWindow(dwn->display, client->window);
|
||||||
|
XSync(dwn->display, False);
|
||||||
} else {
|
} else {
|
||||||
client->flags &= ~CLIENT_FULLSCREEN;
|
client->flags &= ~CLIENT_FULLSCREEN;
|
||||||
|
atoms_update_wm_state(client->window, ewmh.NET_WM_STATE_FULLSCREEN, false);
|
||||||
|
|
||||||
client->x = client->old_x;
|
client->x = client->old_x;
|
||||||
client->y = client->old_y;
|
client->y = client->old_y;
|
||||||
@ -637,6 +637,9 @@ void client_set_fullscreen(Client *client, bool fullscreen)
|
|||||||
XMapWindow(dwn->display, client->frame);
|
XMapWindow(dwn->display, client->frame);
|
||||||
}
|
}
|
||||||
client_configure(client);
|
client_configure(client);
|
||||||
|
decorations_render(client, true);
|
||||||
|
client_raise(client);
|
||||||
|
XSync(dwn->display, False);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
20
src/config.c
20
src/config.c
@ -75,6 +75,14 @@ void config_set_defaults(Config *cfg)
|
|||||||
|
|
||||||
strncpy(cfg->config_path, "~/.config/dwn/config", sizeof(cfg->config_path) - 1);
|
strncpy(cfg->config_path, "~/.config/dwn/config", sizeof(cfg->config_path) - 1);
|
||||||
strncpy(cfg->log_path, "~/.local/share/dwn/dwn.log", sizeof(cfg->log_path) - 1);
|
strncpy(cfg->log_path, "~/.local/share/dwn/dwn.log", sizeof(cfg->log_path) - 1);
|
||||||
|
|
||||||
|
cfg->autostart_enabled = true;
|
||||||
|
cfg->autostart_xdg = true;
|
||||||
|
char *autostart_path = expand_path("~/.config/dwn/autostart.d");
|
||||||
|
if (autostart_path != NULL) {
|
||||||
|
strncpy(cfg->autostart_path, autostart_path, sizeof(cfg->autostart_path) - 1);
|
||||||
|
dwn_free(autostart_path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -152,6 +160,18 @@ static void handle_config_entry(const char *section, const char *key,
|
|||||||
} else if (strcmp(key, "exa_api_key") == 0) {
|
} else if (strcmp(key, "exa_api_key") == 0) {
|
||||||
strncpy(cfg->exa_api_key, value, sizeof(cfg->exa_api_key) - 1);
|
strncpy(cfg->exa_api_key, value, sizeof(cfg->exa_api_key) - 1);
|
||||||
}
|
}
|
||||||
|
} else if (strcmp(section, "autostart") == 0) {
|
||||||
|
if (strcmp(key, "enabled") == 0) {
|
||||||
|
cfg->autostart_enabled = (strcmp(value, "true") == 0 || strcmp(value, "1") == 0);
|
||||||
|
} else if (strcmp(key, "xdg_autostart") == 0) {
|
||||||
|
cfg->autostart_xdg = (strcmp(value, "true") == 0 || strcmp(value, "1") == 0);
|
||||||
|
} else if (strcmp(key, "path") == 0) {
|
||||||
|
char *expanded = expand_path(value);
|
||||||
|
if (expanded != NULL) {
|
||||||
|
strncpy(cfg->autostart_path, expanded, sizeof(cfg->autostart_path) - 1);
|
||||||
|
dwn_free(expanded);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -72,6 +72,9 @@ void decorations_render_title_bar(Client *client, bool focused)
|
|||||||
int text_x = border + BUTTON_PADDING;
|
int text_x = border + BUTTON_PADDING;
|
||||||
|
|
||||||
int max_width = client->width - 3 * (BUTTON_SIZE + BUTTON_PADDING) - 2 * BUTTON_PADDING;
|
int max_width = client->width - 3 * (BUTTON_SIZE + BUTTON_PADDING) - 2 * BUTTON_PADDING;
|
||||||
|
if (max_width < BUTTON_SIZE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
char display_title[256];
|
char display_title[256];
|
||||||
strncpy(display_title, client->title, sizeof(display_title) - 4);
|
strncpy(display_title, client->title, sizeof(display_title) - 4);
|
||||||
display_title[sizeof(display_title) - 4] = '\0';
|
display_title[sizeof(display_title) - 4] = '\0';
|
||||||
@ -138,6 +141,11 @@ void decorations_render_buttons(Client *client, bool focused)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int min_buttons_width = 3 * (BUTTON_SIZE + BUTTON_PADDING) + BUTTON_PADDING;
|
||||||
|
if (client->width < min_buttons_width) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Display *dpy = dwn->display;
|
Display *dpy = dwn->display;
|
||||||
const ColorScheme *colors = config_get_colors();
|
const ColorScheme *colors = config_get_colors();
|
||||||
int title_height = config_get_title_height();
|
int title_height = config_get_title_height();
|
||||||
@ -148,6 +156,10 @@ void decorations_render_buttons(Client *client, bool focused)
|
|||||||
int max_x = close_x - BUTTON_SIZE - BUTTON_PADDING;
|
int max_x = close_x - BUTTON_SIZE - BUTTON_PADDING;
|
||||||
int min_x = max_x - BUTTON_SIZE - BUTTON_PADDING;
|
int min_x = max_x - BUTTON_SIZE - BUTTON_PADDING;
|
||||||
|
|
||||||
|
if (close_x < border || max_x < border || min_x < border) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
unsigned long bg_color = focused ? colors->title_focused_bg : colors->title_unfocused_bg;
|
unsigned long bg_color = focused ? colors->title_focused_bg : colors->title_unfocused_bg;
|
||||||
|
|
||||||
XSetForeground(dpy, dwn->gc, bg_color);
|
XSetForeground(dpy, dwn->gc, bg_color);
|
||||||
|
|||||||
610
src/demo.c
Normal file
610
src/demo.c
Normal file
@ -0,0 +1,610 @@
|
|||||||
|
/*
|
||||||
|
* DWN - Desktop Window Manager
|
||||||
|
* retoor <retoor@molodetz.nl>
|
||||||
|
* Demo mode - automated feature showcase
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "demo.h"
|
||||||
|
#include "dwn.h"
|
||||||
|
#include "notifications.h"
|
||||||
|
#include "workspace.h"
|
||||||
|
#include "client.h"
|
||||||
|
#include "layout.h"
|
||||||
|
#include "keys.h"
|
||||||
|
#include "ai.h"
|
||||||
|
#include "news.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include "panel.h"
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define DEMO_STEP_DELAY 3000
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
bool active;
|
||||||
|
DemoPhase phase;
|
||||||
|
int step;
|
||||||
|
long phase_start_time;
|
||||||
|
long step_start_time;
|
||||||
|
uint32_t current_notification;
|
||||||
|
Window demo_window;
|
||||||
|
int demo_workspace_start;
|
||||||
|
} DemoState;
|
||||||
|
|
||||||
|
static DemoState demo = {0};
|
||||||
|
|
||||||
|
static void demo_notify(const char *title, const char *body, int timeout)
|
||||||
|
{
|
||||||
|
if (demo.current_notification > 0) {
|
||||||
|
notification_close(demo.current_notification);
|
||||||
|
}
|
||||||
|
demo.current_notification = notification_show("DWN Demo", title, body, NULL, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void demo_next_phase(void)
|
||||||
|
{
|
||||||
|
demo.phase++;
|
||||||
|
demo.step = 0;
|
||||||
|
demo.phase_start_time = get_time_ms();
|
||||||
|
demo.step_start_time = demo.phase_start_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void demo_next_step(void)
|
||||||
|
{
|
||||||
|
demo.step++;
|
||||||
|
demo.step_start_time = get_time_ms();
|
||||||
|
}
|
||||||
|
|
||||||
|
static long time_in_step(void)
|
||||||
|
{
|
||||||
|
return get_time_ms() - demo.step_start_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void demo_phase_intro(void)
|
||||||
|
{
|
||||||
|
switch (demo.step) {
|
||||||
|
case 0:
|
||||||
|
demo_notify("Welcome to DWN",
|
||||||
|
"DWN is a modern X11 window manager with:\n\n"
|
||||||
|
"- Tiling, floating, and monocle layouts\n"
|
||||||
|
"- 9 virtual workspaces\n"
|
||||||
|
"- AI-powered command palette\n"
|
||||||
|
"- News ticker with sentiment analysis\n"
|
||||||
|
"- System tray and notifications\n\n"
|
||||||
|
"This demo will showcase all features.\n"
|
||||||
|
"Press Super+Shift+D to stop at any time.", 0);
|
||||||
|
demo_next_step();
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
if (time_in_step() > 5000) {
|
||||||
|
demo_next_phase();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void demo_phase_window_mgmt(void)
|
||||||
|
{
|
||||||
|
Client *c;
|
||||||
|
Workspace *ws;
|
||||||
|
|
||||||
|
switch (demo.step) {
|
||||||
|
case 0:
|
||||||
|
demo_notify("Window Management",
|
||||||
|
"DWN provides complete window management:\n\n"
|
||||||
|
"- Alt+F4: Close window\n"
|
||||||
|
"- Alt+F9: Minimize/restore\n"
|
||||||
|
"- Alt+F10: Maximize\n"
|
||||||
|
"- Alt+F11: Fullscreen\n"
|
||||||
|
"- Alt+Tab: Cycle windows\n\n"
|
||||||
|
"Let's open a terminal...", 0);
|
||||||
|
demo_next_step();
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
if (time_in_step() > 3000) {
|
||||||
|
spawn_async("xterm -title 'Demo Terminal' -e 'echo DWN Demo Terminal; sleep 30' &");
|
||||||
|
demo_next_step();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
if (time_in_step() > 2000) {
|
||||||
|
demo_notify("Window Created",
|
||||||
|
"A terminal window was spawned.\n\n"
|
||||||
|
"Windows can be moved by dragging the title bar\n"
|
||||||
|
"or resized by dragging edges.\n\n"
|
||||||
|
"Let's try minimizing...", 0);
|
||||||
|
demo_next_step();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
if (time_in_step() > 3000) {
|
||||||
|
ws = workspace_get_current();
|
||||||
|
if (ws != NULL && ws->focused != NULL) {
|
||||||
|
demo.demo_window = ws->focused->window;
|
||||||
|
client_minimize(ws->focused);
|
||||||
|
demo_notify("Window Minimized",
|
||||||
|
"The window is now minimized.\n\n"
|
||||||
|
"Look at the taskbar - minimized windows\n"
|
||||||
|
"appear in [brackets] with a gray background.\n\n"
|
||||||
|
"Click it or press Alt+F9 to restore.", 0);
|
||||||
|
}
|
||||||
|
demo_next_step();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
if (time_in_step() > 4000) {
|
||||||
|
c = client_find_by_window(demo.demo_window);
|
||||||
|
if (c != NULL) {
|
||||||
|
client_restore(c);
|
||||||
|
demo_notify("Window Restored",
|
||||||
|
"The window is restored from the taskbar.\n\n"
|
||||||
|
"This is how minimize/restore works in DWN.", 0);
|
||||||
|
}
|
||||||
|
demo_next_step();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
if (time_in_step() > 3000) {
|
||||||
|
c = client_find_by_window(demo.demo_window);
|
||||||
|
if (c != NULL) {
|
||||||
|
client_close(c);
|
||||||
|
}
|
||||||
|
demo_next_phase();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void demo_phase_workspaces(void)
|
||||||
|
{
|
||||||
|
switch (demo.step) {
|
||||||
|
case 0:
|
||||||
|
demo.demo_workspace_start = dwn->current_workspace;
|
||||||
|
demo_notify("Virtual Workspaces",
|
||||||
|
"DWN provides 9 virtual workspaces:\n\n"
|
||||||
|
"- F1 to F9: Switch workspace\n"
|
||||||
|
"- Shift+F1-F9: Move window to workspace\n"
|
||||||
|
"- Ctrl+Alt+Left/Right: Navigate\n\n"
|
||||||
|
"Watch the workspace indicators in the panel...", 0);
|
||||||
|
demo_next_step();
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
if (time_in_step() > 3000) {
|
||||||
|
workspace_switch(1);
|
||||||
|
demo_notify("Workspace 2", "Switched to workspace 2", 1500);
|
||||||
|
demo_next_step();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
if (time_in_step() > 2000) {
|
||||||
|
workspace_switch(2);
|
||||||
|
demo_notify("Workspace 3", "Switched to workspace 3", 1500);
|
||||||
|
demo_next_step();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
if (time_in_step() > 2000) {
|
||||||
|
workspace_switch(3);
|
||||||
|
demo_notify("Workspace 4", "Switched to workspace 4", 1500);
|
||||||
|
demo_next_step();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
if (time_in_step() > 2000) {
|
||||||
|
workspace_switch(demo.demo_workspace_start);
|
||||||
|
demo_notify("Workspaces Complete",
|
||||||
|
"Returned to the original workspace.\n\n"
|
||||||
|
"Each workspace maintains its own:\n"
|
||||||
|
"- Window layout and positions\n"
|
||||||
|
"- Focused window\n"
|
||||||
|
"- Layout mode (tiling/floating/monocle)", 0);
|
||||||
|
demo_next_step();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
if (time_in_step() > 4000) {
|
||||||
|
demo_next_phase();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void demo_phase_layouts(void)
|
||||||
|
{
|
||||||
|
Workspace *ws = workspace_get_current();
|
||||||
|
|
||||||
|
switch (demo.step) {
|
||||||
|
case 0:
|
||||||
|
demo_notify("Layout Modes",
|
||||||
|
"DWN supports 3 layout modes:\n\n"
|
||||||
|
"1. Tiling: Windows auto-arrange without overlap\n"
|
||||||
|
"2. Floating: Free positioning like traditional WMs\n"
|
||||||
|
"3. Monocle: One window fullscreen at a time\n\n"
|
||||||
|
"Press Super+Space to cycle layouts.", 0);
|
||||||
|
demo_next_step();
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
if (time_in_step() > 4000) {
|
||||||
|
if (ws != NULL) {
|
||||||
|
ws->layout = LAYOUT_TILING;
|
||||||
|
workspace_arrange_current();
|
||||||
|
}
|
||||||
|
demo_notify("Tiling Layout",
|
||||||
|
"In tiling mode, windows are automatically\n"
|
||||||
|
"arranged in a main + stack pattern.\n\n"
|
||||||
|
"- Super+H/L: Resize main area\n"
|
||||||
|
"- Super+I/D: Add/remove from main\n\n"
|
||||||
|
"No overlapping windows - efficient use of space.", 0);
|
||||||
|
demo_next_step();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
if (time_in_step() > 4000) {
|
||||||
|
if (ws != NULL) {
|
||||||
|
ws->layout = LAYOUT_FLOATING;
|
||||||
|
workspace_arrange_current();
|
||||||
|
}
|
||||||
|
demo_notify("Floating Layout",
|
||||||
|
"In floating mode, windows can be freely\n"
|
||||||
|
"positioned and resized anywhere.\n\n"
|
||||||
|
"This is the traditional desktop behavior\n"
|
||||||
|
"familiar from Windows and macOS.", 0);
|
||||||
|
demo_next_step();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
if (time_in_step() > 4000) {
|
||||||
|
if (ws != NULL) {
|
||||||
|
ws->layout = LAYOUT_MONOCLE;
|
||||||
|
workspace_arrange_current();
|
||||||
|
}
|
||||||
|
demo_notify("Monocle Layout",
|
||||||
|
"In monocle mode, each window takes\n"
|
||||||
|
"the full screen.\n\n"
|
||||||
|
"Use Alt+Tab to switch between windows.\n"
|
||||||
|
"Great for focused, single-task work.", 0);
|
||||||
|
demo_next_step();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
if (time_in_step() > 4000) {
|
||||||
|
if (ws != NULL) {
|
||||||
|
ws->layout = LAYOUT_TILING;
|
||||||
|
workspace_arrange_current();
|
||||||
|
}
|
||||||
|
demo_next_phase();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void demo_phase_snapping(void)
|
||||||
|
{
|
||||||
|
switch (demo.step) {
|
||||||
|
case 0:
|
||||||
|
demo_notify("Window Snapping",
|
||||||
|
"Quickly arrange windows with snapping:\n\n"
|
||||||
|
"- Super+Left: Snap to left half (50%)\n"
|
||||||
|
"- Super+Right: Snap to right half (50%)\n\n"
|
||||||
|
"Perfect for side-by-side comparisons\n"
|
||||||
|
"or working with multiple documents.", 0);
|
||||||
|
demo_next_step();
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
if (time_in_step() > 4000) {
|
||||||
|
demo_next_phase();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void demo_phase_panels(void)
|
||||||
|
{
|
||||||
|
switch (demo.step) {
|
||||||
|
case 0:
|
||||||
|
demo_notify("Panel System",
|
||||||
|
"DWN has two panels with rich functionality:\n\n"
|
||||||
|
"TOP PANEL (left to right):\n"
|
||||||
|
"- Workspace indicators (click to switch)\n"
|
||||||
|
"- Layout mode indicator\n"
|
||||||
|
"- Taskbar (click to focus/restore)\n"
|
||||||
|
"- AI status indicator\n"
|
||||||
|
"- System tray icons\n\n"
|
||||||
|
"BOTTOM PANEL:\n"
|
||||||
|
"- Clock with date\n"
|
||||||
|
"- News ticker", 0);
|
||||||
|
demo_next_step();
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
if (time_in_step() > 5000) {
|
||||||
|
demo_notify("System Tray",
|
||||||
|
"The system tray shows:\n\n"
|
||||||
|
"- Battery level (color-coded)\n"
|
||||||
|
"- WiFi status (click for network list)\n"
|
||||||
|
"- Volume (scroll to adjust, click to mute)\n"
|
||||||
|
"- External app icons (Telegram, etc.)\n\n"
|
||||||
|
"DWN implements the XEmbed protocol for\n"
|
||||||
|
"full compatibility with tray applications.", 0);
|
||||||
|
demo_next_step();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
if (time_in_step() > 5000) {
|
||||||
|
demo_next_phase();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void demo_phase_ai(void)
|
||||||
|
{
|
||||||
|
switch (demo.step) {
|
||||||
|
case 0:
|
||||||
|
if (dwn->ai_enabled) {
|
||||||
|
demo_notify("AI Integration",
|
||||||
|
"DWN includes AI-powered features:\n\n"
|
||||||
|
"COMMAND PALETTE (Super+Shift+A):\n"
|
||||||
|
"- Ask AI to launch apps: 'open firefox'\n"
|
||||||
|
"- Get answers: 'what time is it'\n"
|
||||||
|
"- Execute commands naturally\n\n"
|
||||||
|
"CONTEXT ANALYSIS (Super+A):\n"
|
||||||
|
"- See what you're working on\n"
|
||||||
|
"- Get AI suggestions based on context\n\n"
|
||||||
|
"EXA SEARCH (Super+Shift+E):\n"
|
||||||
|
"- Semantic web search\n"
|
||||||
|
"- Find relevant content by meaning", 0);
|
||||||
|
} else {
|
||||||
|
demo_notify("AI Integration",
|
||||||
|
"DWN includes AI-powered features:\n\n"
|
||||||
|
"AI is currently DISABLED.\n"
|
||||||
|
"To enable, set these environment variables:\n\n"
|
||||||
|
"OPENROUTER_API_KEY=sk-or-v1-your-key\n"
|
||||||
|
"EXA_API_KEY=your-exa-key\n\n"
|
||||||
|
"Then restart DWN to enable:\n"
|
||||||
|
"- AI Command Palette (Super+Shift+A)\n"
|
||||||
|
"- Context Analysis (Super+A)\n"
|
||||||
|
"- Exa Semantic Search (Super+Shift+E)", 0);
|
||||||
|
}
|
||||||
|
demo_next_step();
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
if (time_in_step() > 6000) {
|
||||||
|
demo_next_phase();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void demo_phase_news(void)
|
||||||
|
{
|
||||||
|
switch (demo.step) {
|
||||||
|
case 0:
|
||||||
|
demo_notify("News Ticker",
|
||||||
|
"The bottom panel includes a news ticker:\n\n"
|
||||||
|
"- Headlines scroll automatically\n"
|
||||||
|
"- Color-coded by sentiment:\n"
|
||||||
|
" Green = Positive\n"
|
||||||
|
" Red = Negative\n"
|
||||||
|
" White = Neutral\n\n"
|
||||||
|
"CONTROLS:\n"
|
||||||
|
"- Super+Down: Next article\n"
|
||||||
|
"- Super+Up: Previous article\n"
|
||||||
|
"- Super+Return: Open in browser", 0);
|
||||||
|
demo_next_step();
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
if (time_in_step() > 3000) {
|
||||||
|
news_next_article();
|
||||||
|
demo_notify("News Navigation", "Moved to next article", 1500);
|
||||||
|
demo_next_step();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
if (time_in_step() > 2000) {
|
||||||
|
news_next_article();
|
||||||
|
demo_notify("News Navigation", "Moved to next article", 1500);
|
||||||
|
demo_next_step();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
if (time_in_step() > 2000) {
|
||||||
|
news_prev_article();
|
||||||
|
demo_notify("News Navigation", "Moved to previous article", 1500);
|
||||||
|
demo_next_step();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
if (time_in_step() > 2000) {
|
||||||
|
demo_next_phase();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void demo_phase_shortcuts(void)
|
||||||
|
{
|
||||||
|
switch (demo.step) {
|
||||||
|
case 0:
|
||||||
|
demo_notify("Quick Reference",
|
||||||
|
"ESSENTIAL SHORTCUTS:\n\n"
|
||||||
|
"Ctrl+Alt+T Open terminal\n"
|
||||||
|
"Super App launcher\n"
|
||||||
|
"Alt+F4 Close window\n"
|
||||||
|
"Alt+Tab Cycle windows\n"
|
||||||
|
"Super+Space Cycle layouts\n\n"
|
||||||
|
"WORKSPACES:\n"
|
||||||
|
"F1-F9 Switch workspace\n"
|
||||||
|
"Shift+F1-F9 Move window\n\n"
|
||||||
|
"WINDOW CONTROL:\n"
|
||||||
|
"Alt+F9 Minimize/restore\n"
|
||||||
|
"Alt+F10 Maximize\n"
|
||||||
|
"Alt+F11 Fullscreen\n"
|
||||||
|
"Super+Left Snap left\n"
|
||||||
|
"Super+Right Snap right\n\n"
|
||||||
|
"Press Super+S anytime to see all shortcuts.", 0);
|
||||||
|
demo_next_step();
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
if (time_in_step() > 8000) {
|
||||||
|
demo_next_phase();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void demo_phase_complete(void)
|
||||||
|
{
|
||||||
|
switch (demo.step) {
|
||||||
|
case 0:
|
||||||
|
demo_notify("Demo Complete",
|
||||||
|
"Thank you for watching the DWN demo!\n\n"
|
||||||
|
"NEXT STEPS:\n"
|
||||||
|
"- Super+T: Start interactive tutorial\n"
|
||||||
|
"- Super+S: View all shortcuts\n"
|
||||||
|
"- Super+Shift+D: Run this demo again\n\n"
|
||||||
|
"CONFIGURATION:\n"
|
||||||
|
"Edit ~/.config/dwn/config to customize\n"
|
||||||
|
"colors, fonts, keybindings, and more.\n\n"
|
||||||
|
"Enjoy using DWN!", 0);
|
||||||
|
demo_next_step();
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
if (time_in_step() > 6000) {
|
||||||
|
notification_close(demo.current_notification);
|
||||||
|
demo.current_notification = 0;
|
||||||
|
demo.active = false;
|
||||||
|
demo.phase = DEMO_IDLE;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void demo_init(void)
|
||||||
|
{
|
||||||
|
memset(&demo, 0, sizeof(demo));
|
||||||
|
demo.phase = DEMO_IDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void demo_cleanup(void)
|
||||||
|
{
|
||||||
|
if (demo.current_notification > 0) {
|
||||||
|
notification_close(demo.current_notification);
|
||||||
|
}
|
||||||
|
memset(&demo, 0, sizeof(demo));
|
||||||
|
}
|
||||||
|
|
||||||
|
void demo_start(void)
|
||||||
|
{
|
||||||
|
if (demo.active) {
|
||||||
|
demo_stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tutorial_is_active()) {
|
||||||
|
demo_notify("Demo Unavailable",
|
||||||
|
"Cannot start demo while tutorial is running.\n"
|
||||||
|
"Please complete or stop the tutorial first.", 3000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
demo.active = true;
|
||||||
|
demo.phase = DEMO_INTRO;
|
||||||
|
demo.step = 0;
|
||||||
|
demo.phase_start_time = get_time_ms();
|
||||||
|
demo.step_start_time = demo.phase_start_time;
|
||||||
|
demo.demo_workspace_start = dwn->current_workspace;
|
||||||
|
}
|
||||||
|
|
||||||
|
void demo_stop(void)
|
||||||
|
{
|
||||||
|
if (!demo.active) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (demo.current_notification > 0) {
|
||||||
|
notification_close(demo.current_notification);
|
||||||
|
demo.current_notification = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
demo_notify("Demo Stopped", "Demo mode has been stopped.", 2000);
|
||||||
|
|
||||||
|
demo.active = false;
|
||||||
|
demo.phase = DEMO_IDLE;
|
||||||
|
demo.step = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void demo_update(void)
|
||||||
|
{
|
||||||
|
if (!demo.active) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (demo.phase) {
|
||||||
|
case DEMO_IDLE:
|
||||||
|
break;
|
||||||
|
case DEMO_INTRO:
|
||||||
|
demo_phase_intro();
|
||||||
|
break;
|
||||||
|
case DEMO_WINDOW_MGMT:
|
||||||
|
demo_phase_window_mgmt();
|
||||||
|
break;
|
||||||
|
case DEMO_WORKSPACES:
|
||||||
|
demo_phase_workspaces();
|
||||||
|
break;
|
||||||
|
case DEMO_LAYOUTS:
|
||||||
|
demo_phase_layouts();
|
||||||
|
break;
|
||||||
|
case DEMO_SNAPPING:
|
||||||
|
demo_phase_snapping();
|
||||||
|
break;
|
||||||
|
case DEMO_PANELS:
|
||||||
|
demo_phase_panels();
|
||||||
|
break;
|
||||||
|
case DEMO_AI:
|
||||||
|
demo_phase_ai();
|
||||||
|
break;
|
||||||
|
case DEMO_NEWS:
|
||||||
|
demo_phase_news();
|
||||||
|
break;
|
||||||
|
case DEMO_SHORTCUTS:
|
||||||
|
demo_phase_shortcuts();
|
||||||
|
break;
|
||||||
|
case DEMO_COMPLETE:
|
||||||
|
demo_phase_complete();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool demo_is_active(void)
|
||||||
|
{
|
||||||
|
return demo.active;
|
||||||
|
}
|
||||||
|
|
||||||
|
DemoPhase demo_get_phase(void)
|
||||||
|
{
|
||||||
|
return demo.phase;
|
||||||
|
}
|
||||||
|
|
||||||
|
int demo_get_step(void)
|
||||||
|
{
|
||||||
|
return demo.step;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *demo_get_phase_name(DemoPhase phase)
|
||||||
|
{
|
||||||
|
switch (phase) {
|
||||||
|
case DEMO_IDLE: return "Idle";
|
||||||
|
case DEMO_INTRO: return "Introduction";
|
||||||
|
case DEMO_WINDOW_MGMT: return "Window Management";
|
||||||
|
case DEMO_WORKSPACES: return "Workspaces";
|
||||||
|
case DEMO_LAYOUTS: return "Layouts";
|
||||||
|
case DEMO_SNAPPING: return "Snapping";
|
||||||
|
case DEMO_PANELS: return "Panels";
|
||||||
|
case DEMO_AI: return "AI Features";
|
||||||
|
case DEMO_NEWS: return "News Ticker";
|
||||||
|
case DEMO_SHORTCUTS: return "Shortcuts";
|
||||||
|
case DEMO_COMPLETE: return "Complete";
|
||||||
|
default: return "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
122
src/keys.c
122
src/keys.c
@ -13,6 +13,8 @@
|
|||||||
#include "notifications.h"
|
#include "notifications.h"
|
||||||
#include "news.h"
|
#include "news.h"
|
||||||
#include "applauncher.h"
|
#include "applauncher.h"
|
||||||
|
#include "decorations.h"
|
||||||
|
#include "demo.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@ -470,6 +472,8 @@ void keys_setup_defaults(void)
|
|||||||
|
|
||||||
keys_bind(MOD_ALT, XK_F4, key_close_window, "Close window");
|
keys_bind(MOD_ALT, XK_F4, key_close_window, "Close window");
|
||||||
|
|
||||||
|
keys_bind(MOD_ALT, XK_F9, key_toggle_minimize, "Toggle minimize");
|
||||||
|
|
||||||
keys_bind(MOD_ALT, XK_F10, key_toggle_maximize, "Toggle maximize");
|
keys_bind(MOD_ALT, XK_F10, key_toggle_maximize, "Toggle maximize");
|
||||||
|
|
||||||
keys_bind(MOD_ALT, XK_F11, key_toggle_fullscreen, "Toggle fullscreen");
|
keys_bind(MOD_ALT, XK_F11, key_toggle_fullscreen, "Toggle fullscreen");
|
||||||
@ -512,11 +516,16 @@ void keys_setup_defaults(void)
|
|||||||
keys_bind(MOD_SUPER, XK_i, key_increase_master_count, "Increase master count");
|
keys_bind(MOD_SUPER, XK_i, key_increase_master_count, "Increase master count");
|
||||||
keys_bind(MOD_SUPER, XK_d, key_decrease_master_count, "Decrease master count");
|
keys_bind(MOD_SUPER, XK_d, key_decrease_master_count, "Decrease master count");
|
||||||
|
|
||||||
|
keys_bind(MOD_SUPER, XK_Left, key_snap_left, "Snap window left");
|
||||||
|
keys_bind(MOD_SUPER, XK_Right, key_snap_right, "Snap window right");
|
||||||
|
|
||||||
keys_bind(MOD_SUPER, XK_a, key_toggle_ai, "Toggle AI context");
|
keys_bind(MOD_SUPER, XK_a, key_toggle_ai, "Toggle AI context");
|
||||||
keys_bind(MOD_SUPER | MOD_SHIFT, XK_a, key_ai_command, "AI command");
|
keys_bind(MOD_SUPER | MOD_SHIFT, XK_a, key_ai_command, "AI command");
|
||||||
|
|
||||||
keys_bind(MOD_SUPER | MOD_SHIFT, XK_e, key_exa_search, "Exa web search");
|
keys_bind(MOD_SUPER | MOD_SHIFT, XK_e, key_exa_search, "Exa web search");
|
||||||
|
|
||||||
|
keys_bind(MOD_SUPER | MOD_SHIFT, XK_d, key_start_demo, "Start/stop demo mode");
|
||||||
|
|
||||||
keys_bind(MOD_SUPER, XK_s, key_show_shortcuts, "Show shortcuts");
|
keys_bind(MOD_SUPER, XK_s, key_show_shortcuts, "Show shortcuts");
|
||||||
|
|
||||||
keys_bind(MOD_SUPER, XK_t, key_start_tutorial, "Start tutorial");
|
keys_bind(MOD_SUPER, XK_t, key_start_tutorial, "Start tutorial");
|
||||||
@ -603,6 +612,20 @@ void key_toggle_maximize(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void key_toggle_minimize(void)
|
||||||
|
{
|
||||||
|
Workspace *ws = workspace_get_current();
|
||||||
|
if (ws == NULL || ws->focused == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client_is_minimized(ws->focused)) {
|
||||||
|
client_restore(ws->focused);
|
||||||
|
} else {
|
||||||
|
client_minimize(ws->focused);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void key_focus_next(void)
|
void key_focus_next(void)
|
||||||
{
|
{
|
||||||
workspace_focus_next();
|
workspace_focus_next();
|
||||||
@ -762,6 +785,7 @@ void key_show_shortcuts(void)
|
|||||||
"Alt+F4 Close window\n"
|
"Alt+F4 Close window\n"
|
||||||
"Alt+Tab Next window\n"
|
"Alt+Tab Next window\n"
|
||||||
"Alt+Shift+Tab Previous window\n"
|
"Alt+Shift+Tab Previous window\n"
|
||||||
|
"Alt+F9 Toggle minimize\n"
|
||||||
"Alt+F10 Toggle maximize\n"
|
"Alt+F10 Toggle maximize\n"
|
||||||
"Alt+F11 Toggle fullscreen\n"
|
"Alt+F11 Toggle fullscreen\n"
|
||||||
"Super+F9 Toggle floating\n"
|
"Super+F9 Toggle floating\n"
|
||||||
@ -778,6 +802,8 @@ void key_show_shortcuts(void)
|
|||||||
"Super+L Expand master area\n"
|
"Super+L Expand master area\n"
|
||||||
"Super+I Add to master\n"
|
"Super+I Add to master\n"
|
||||||
"Super+D Remove from master\n"
|
"Super+D Remove from master\n"
|
||||||
|
"Super+Left Snap window left (50%)\n"
|
||||||
|
"Super+Right Snap window right (50%)\n"
|
||||||
"\n"
|
"\n"
|
||||||
"=== AI Features ===\n"
|
"=== AI Features ===\n"
|
||||||
"Super+A Show AI context\n"
|
"Super+A Show AI context\n"
|
||||||
@ -787,6 +813,7 @@ void key_show_shortcuts(void)
|
|||||||
"=== Help & System ===\n"
|
"=== Help & System ===\n"
|
||||||
"Super+S Show shortcuts (this)\n"
|
"Super+S Show shortcuts (this)\n"
|
||||||
"Super+T Interactive tutorial\n"
|
"Super+T Interactive tutorial\n"
|
||||||
|
"Super+Shift+D Demo mode\n"
|
||||||
"Super+Backspace Quit DWN";
|
"Super+Backspace Quit DWN";
|
||||||
|
|
||||||
notification_show("DWN Shortcuts", "Complete Reference", shortcuts, NULL, 0);
|
notification_show("DWN Shortcuts", "Complete Reference", shortcuts, NULL, 0);
|
||||||
@ -821,3 +848,98 @@ void key_screenshot(void)
|
|||||||
{
|
{
|
||||||
spawn_async("xfce4-screenshooter");
|
spawn_async("xfce4-screenshooter");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void key_snap_left(void)
|
||||||
|
{
|
||||||
|
if (dwn == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Workspace *ws = workspace_get_current();
|
||||||
|
if (ws == NULL || ws->focused == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Client *c = ws->focused;
|
||||||
|
|
||||||
|
int work_x = 0;
|
||||||
|
int work_y = 0;
|
||||||
|
int work_width = dwn->screen_width;
|
||||||
|
int work_height = dwn->screen_height;
|
||||||
|
|
||||||
|
if (dwn->config != NULL) {
|
||||||
|
if (dwn->config->top_panel_enabled) {
|
||||||
|
work_y += config_get_panel_height();
|
||||||
|
work_height -= config_get_panel_height();
|
||||||
|
}
|
||||||
|
if (dwn->config->bottom_panel_enabled) {
|
||||||
|
work_height -= config_get_panel_height();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int gap = config_get_gap();
|
||||||
|
|
||||||
|
if (!client_is_floating(c)) {
|
||||||
|
client_set_floating(c, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
c->x = work_x + gap;
|
||||||
|
c->y = work_y + gap;
|
||||||
|
c->width = (work_width / 2) - (gap * 2);
|
||||||
|
c->height = work_height - (gap * 2);
|
||||||
|
|
||||||
|
client_configure(c);
|
||||||
|
decorations_render(c, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void key_snap_right(void)
|
||||||
|
{
|
||||||
|
if (dwn == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Workspace *ws = workspace_get_current();
|
||||||
|
if (ws == NULL || ws->focused == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Client *c = ws->focused;
|
||||||
|
|
||||||
|
int work_x = 0;
|
||||||
|
int work_y = 0;
|
||||||
|
int work_width = dwn->screen_width;
|
||||||
|
int work_height = dwn->screen_height;
|
||||||
|
|
||||||
|
if (dwn->config != NULL) {
|
||||||
|
if (dwn->config->top_panel_enabled) {
|
||||||
|
work_y += config_get_panel_height();
|
||||||
|
work_height -= config_get_panel_height();
|
||||||
|
}
|
||||||
|
if (dwn->config->bottom_panel_enabled) {
|
||||||
|
work_height -= config_get_panel_height();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int gap = config_get_gap();
|
||||||
|
|
||||||
|
if (!client_is_floating(c)) {
|
||||||
|
client_set_floating(c, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
c->x = work_x + (work_width / 2) + gap;
|
||||||
|
c->y = work_y + gap;
|
||||||
|
c->width = (work_width / 2) - (gap * 2);
|
||||||
|
c->height = work_height - (gap * 2);
|
||||||
|
|
||||||
|
client_configure(c);
|
||||||
|
decorations_render(c, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void key_start_demo(void)
|
||||||
|
{
|
||||||
|
if (demo_is_active()) {
|
||||||
|
demo_stop();
|
||||||
|
} else {
|
||||||
|
demo_start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
32
src/main.c
32
src/main.c
@ -18,6 +18,8 @@
|
|||||||
#include "news.h"
|
#include "news.h"
|
||||||
#include "applauncher.h"
|
#include "applauncher.h"
|
||||||
#include "ai.h"
|
#include "ai.h"
|
||||||
|
#include "autostart.h"
|
||||||
|
#include "demo.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@ -251,6 +253,11 @@ static void handle_destroy_notify(XDestroyWindowEvent *ev)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (xembed_find_icon(ev->window) != NULL) {
|
||||||
|
xembed_remove_icon(ev->window);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Client *c = client_find_by_window(ev->window);
|
Client *c = client_find_by_window(ev->window);
|
||||||
if (c != NULL) {
|
if (c != NULL) {
|
||||||
client_unmanage(c);
|
client_unmanage(c);
|
||||||
@ -295,6 +302,13 @@ static void handle_property_notify(XPropertyEvent *ev)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ev->atom == ewmh.XEMBED_INFO) {
|
||||||
|
if (xembed_find_icon(ev->window) != NULL) {
|
||||||
|
xembed_update_icon_state(ev->window);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Client *c = client_find_by_window(ev->window);
|
Client *c = client_find_by_window(ev->window);
|
||||||
if (c == NULL) {
|
if (c == NULL) {
|
||||||
return;
|
return;
|
||||||
@ -560,6 +574,12 @@ static void handle_client_message(XClientMessageEvent *ev)
|
|||||||
if (desktop >= 0 && desktop < MAX_WORKSPACES) {
|
if (desktop >= 0 && desktop < MAX_WORKSPACES) {
|
||||||
workspace_switch(desktop);
|
workspace_switch(desktop);
|
||||||
}
|
}
|
||||||
|
} else if (ev->message_type == ewmh.NET_SYSTEM_TRAY_OPCODE) {
|
||||||
|
long opcode = ev->data.l[1];
|
||||||
|
if (opcode == SYSTEM_TRAY_REQUEST_DOCK) {
|
||||||
|
Window icon_win = ev->data.l[2];
|
||||||
|
xembed_dock_icon(icon_win);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -708,16 +728,23 @@ int dwn_init(void)
|
|||||||
|
|
||||||
systray_init();
|
systray_init();
|
||||||
|
|
||||||
|
xembed_init();
|
||||||
|
|
||||||
news_init();
|
news_init();
|
||||||
|
|
||||||
applauncher_init();
|
applauncher_init();
|
||||||
|
|
||||||
|
autostart_init();
|
||||||
|
autostart_run();
|
||||||
|
|
||||||
keys_init();
|
keys_init();
|
||||||
|
|
||||||
notifications_init();
|
notifications_init();
|
||||||
|
|
||||||
ai_init();
|
ai_init();
|
||||||
|
|
||||||
|
demo_init();
|
||||||
|
|
||||||
atoms_setup_ewmh();
|
atoms_setup_ewmh();
|
||||||
|
|
||||||
XSelectInput(dwn->display, dwn->root,
|
XSelectInput(dwn->display, dwn->root,
|
||||||
@ -745,11 +772,14 @@ void dwn_cleanup(void)
|
|||||||
{
|
{
|
||||||
LOG_INFO("DWN shutting down");
|
LOG_INFO("DWN shutting down");
|
||||||
|
|
||||||
|
demo_cleanup();
|
||||||
ai_cleanup();
|
ai_cleanup();
|
||||||
notifications_cleanup();
|
notifications_cleanup();
|
||||||
news_cleanup();
|
news_cleanup();
|
||||||
applauncher_cleanup();
|
applauncher_cleanup();
|
||||||
|
autostart_cleanup();
|
||||||
keys_cleanup();
|
keys_cleanup();
|
||||||
|
xembed_cleanup();
|
||||||
systray_cleanup();
|
systray_cleanup();
|
||||||
panels_cleanup();
|
panels_cleanup();
|
||||||
decorations_cleanup();
|
decorations_cleanup();
|
||||||
@ -806,6 +836,8 @@ void dwn_run(void)
|
|||||||
|
|
||||||
exa_process_pending();
|
exa_process_pending();
|
||||||
|
|
||||||
|
demo_update();
|
||||||
|
|
||||||
notifications_update();
|
notifications_update();
|
||||||
|
|
||||||
long now = get_time_ms();
|
long now = get_time_ms();
|
||||||
|
|||||||
35
src/news.c
35
src/news.c
@ -17,6 +17,7 @@
|
|||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
|
#include <stdatomic.h>
|
||||||
#include <curl/curl.h>
|
#include <curl/curl.h>
|
||||||
#include <X11/Xft/Xft.h>
|
#include <X11/Xft/Xft.h>
|
||||||
|
|
||||||
@ -41,7 +42,7 @@ NewsState news_state = {0};
|
|||||||
|
|
||||||
static pthread_mutex_t news_mutex = PTHREAD_MUTEX_INITIALIZER;
|
static pthread_mutex_t news_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||||
static pthread_t fetch_thread;
|
static pthread_t fetch_thread;
|
||||||
static volatile int fetch_running = 0;
|
static atomic_int fetch_running = 0;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char *data;
|
char *data;
|
||||||
@ -428,9 +429,9 @@ void news_init(void)
|
|||||||
|
|
||||||
void news_cleanup(void)
|
void news_cleanup(void)
|
||||||
{
|
{
|
||||||
if (fetch_running) {
|
if (atomic_load(&fetch_running)) {
|
||||||
pthread_join(fetch_thread, NULL);
|
pthread_join(fetch_thread, NULL);
|
||||||
fetch_running = 0;
|
atomic_store(&fetch_running, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -444,17 +445,17 @@ void news_fetch_async(void)
|
|||||||
news_state.fetching = true;
|
news_state.fetching = true;
|
||||||
pthread_mutex_unlock(&news_mutex);
|
pthread_mutex_unlock(&news_mutex);
|
||||||
|
|
||||||
if (fetch_running) {
|
if (atomic_load(&fetch_running)) {
|
||||||
pthread_join(fetch_thread, NULL);
|
pthread_join(fetch_thread, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch_running = 1;
|
atomic_store(&fetch_running, 1);
|
||||||
if (pthread_create(&fetch_thread, NULL, news_fetch_thread, NULL) != 0) {
|
if (pthread_create(&fetch_thread, NULL, news_fetch_thread, NULL) != 0) {
|
||||||
pthread_mutex_lock(&news_mutex);
|
pthread_mutex_lock(&news_mutex);
|
||||||
news_state.fetching = false;
|
news_state.fetching = false;
|
||||||
news_state.has_error = true;
|
news_state.has_error = true;
|
||||||
pthread_mutex_unlock(&news_mutex);
|
pthread_mutex_unlock(&news_mutex);
|
||||||
fetch_running = 0;
|
atomic_store(&fetch_running, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -578,6 +579,15 @@ void news_render(Panel *panel, int x, int max_width, int *used_width)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int text_y;
|
||||||
|
if (dwn->xft_font != NULL) {
|
||||||
|
text_y = (panel->height + dwn->xft_font->ascent - dwn->xft_font->descent) / 2;
|
||||||
|
} else {
|
||||||
|
text_y = (panel->height + dwn->font->ascent - dwn->font->descent) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ColorScheme *colors = config_get_colors();
|
||||||
|
|
||||||
pthread_mutex_lock(&news_mutex);
|
pthread_mutex_lock(&news_mutex);
|
||||||
|
|
||||||
if (news_state.article_count == 0) {
|
if (news_state.article_count == 0) {
|
||||||
@ -603,21 +613,8 @@ void news_render(Panel *panel, int x, int max_width, int *used_width)
|
|||||||
int separator_width = news_text_width(NEWS_SEPARATOR);
|
int separator_width = news_text_width(NEWS_SEPARATOR);
|
||||||
int scroll_offset = (int)news_state.scroll_offset % news_state.total_width;
|
int scroll_offset = (int)news_state.scroll_offset % news_state.total_width;
|
||||||
|
|
||||||
pthread_mutex_unlock(&news_mutex);
|
|
||||||
|
|
||||||
const ColorScheme *colors = config_get_colors();
|
|
||||||
|
|
||||||
int text_y;
|
|
||||||
if (dwn->xft_font != NULL) {
|
|
||||||
text_y = (panel->height + dwn->xft_font->ascent - dwn->xft_font->descent) / 2;
|
|
||||||
} else {
|
|
||||||
text_y = (panel->height + dwn->font->ascent - dwn->font->descent) / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
XRectangle clip_rect = { x, 0, max_width, panel->height };
|
XRectangle clip_rect = { x, 0, max_width, panel->height };
|
||||||
|
|
||||||
pthread_mutex_lock(&news_mutex);
|
|
||||||
|
|
||||||
int draw_x = x - scroll_offset;
|
int draw_x = x - scroll_offset;
|
||||||
int articles_drawn = 0;
|
int articles_drawn = 0;
|
||||||
int start_article = 0;
|
int start_article = 0;
|
||||||
|
|||||||
@ -10,8 +10,21 @@
|
|||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <setjmp.h>
|
||||||
|
#include <unistd.h>
|
||||||
#include <X11/Xft/Xft.h>
|
#include <X11/Xft/Xft.h>
|
||||||
|
|
||||||
|
static sigjmp_buf dbus_timeout_jmp;
|
||||||
|
static volatile sig_atomic_t dbus_timeout_occurred = 0;
|
||||||
|
|
||||||
|
static void dbus_timeout_handler(int sig)
|
||||||
|
{
|
||||||
|
(void)sig;
|
||||||
|
dbus_timeout_occurred = 1;
|
||||||
|
siglongjmp(dbus_timeout_jmp, 1);
|
||||||
|
}
|
||||||
|
|
||||||
DBusConnection *dbus_conn = NULL;
|
DBusConnection *dbus_conn = NULL;
|
||||||
|
|
||||||
static Notification *notification_list = NULL;
|
static Notification *notification_list = NULL;
|
||||||
@ -124,7 +137,27 @@ bool notifications_init(void)
|
|||||||
DBusError err;
|
DBusError err;
|
||||||
dbus_error_init(&err);
|
dbus_error_init(&err);
|
||||||
|
|
||||||
|
dbus_timeout_occurred = 0;
|
||||||
|
|
||||||
|
struct sigaction sa, old_sa;
|
||||||
|
sa.sa_handler = dbus_timeout_handler;
|
||||||
|
sigemptyset(&sa.sa_mask);
|
||||||
|
sa.sa_flags = 0;
|
||||||
|
sigaction(SIGALRM, &sa, &old_sa);
|
||||||
|
|
||||||
|
if (sigsetjmp(dbus_timeout_jmp, 1) != 0) {
|
||||||
|
sigaction(SIGALRM, &old_sa, NULL);
|
||||||
|
alarm(0);
|
||||||
|
LOG_WARN("D-Bus connection timed out (5s) - notifications disabled");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
alarm(5);
|
||||||
dbus_conn = dbus_bus_get(DBUS_BUS_SESSION, &err);
|
dbus_conn = dbus_bus_get(DBUS_BUS_SESSION, &err);
|
||||||
|
alarm(0);
|
||||||
|
|
||||||
|
sigaction(SIGALRM, &old_sa, NULL);
|
||||||
|
|
||||||
if (dbus_error_is_set(&err)) {
|
if (dbus_error_is_set(&err)) {
|
||||||
LOG_ERROR("D-Bus connection error: %s", err.message);
|
LOG_ERROR("D-Bus connection error: %s", err.message);
|
||||||
dbus_error_free(&err);
|
dbus_error_free(&err);
|
||||||
@ -731,10 +764,19 @@ void notifications_position(void)
|
|||||||
y += config_get_panel_height();
|
y += config_get_panel_height();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int max_y = dwn->screen_height - NOTIFICATION_MARGIN;
|
||||||
|
|
||||||
for (Notification *n = notification_list; n != NULL; n = n->next) {
|
for (Notification *n = notification_list; n != NULL; n = n->next) {
|
||||||
|
if (y + n->height > max_y) {
|
||||||
|
if (n->window != None) {
|
||||||
|
XUnmapWindow(dwn->display, n->window);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (n->window != None) {
|
if (n->window != None) {
|
||||||
int x = dwn->screen_width - n->width - NOTIFICATION_MARGIN;
|
int x = dwn->screen_width - n->width - NOTIFICATION_MARGIN;
|
||||||
XMoveWindow(dwn->display, n->window, x, y);
|
XMoveWindow(dwn->display, n->window, x, y);
|
||||||
|
XMapRaised(dwn->display, n->window);
|
||||||
}
|
}
|
||||||
y += n->height + NOTIFICATION_MARGIN;
|
y += n->height + NOTIFICATION_MARGIN;
|
||||||
}
|
}
|
||||||
|
|||||||
71
src/panel.c
71
src/panel.c
@ -346,11 +346,15 @@ void panel_render_taskbar(Panel *panel, int x, int *width)
|
|||||||
int text_y = panel_text_y(panel->height);
|
int text_y = panel_text_y(panel->height);
|
||||||
|
|
||||||
int current_x = x;
|
int current_x = x;
|
||||||
int available_width = panel->width - x - 100 - PANEL_PADDING;
|
int systray_actual_width = systray_get_width();
|
||||||
|
int ai_width = dwn->ai_enabled ? 60 : 0;
|
||||||
|
int right_reserve = systray_actual_width + ai_width + PANEL_PADDING * 2;
|
||||||
|
int available_width = panel->width - x - right_reserve;
|
||||||
|
if (available_width < 0) available_width = 0;
|
||||||
int item_count = 0;
|
int item_count = 0;
|
||||||
|
|
||||||
for (Client *c = dwn->client_list; c != NULL; c = c->next) {
|
for (Client *c = dwn->client_list; c != NULL; c = c->next) {
|
||||||
if (c->workspace == (unsigned int)dwn->current_workspace && !client_is_minimized(c)) {
|
if (c->workspace == (unsigned int)dwn->current_workspace) {
|
||||||
item_count++;
|
item_count++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -368,20 +372,32 @@ void panel_render_taskbar(Panel *panel, int x, int *width)
|
|||||||
Workspace *ws = workspace_get_current();
|
Workspace *ws = workspace_get_current();
|
||||||
|
|
||||||
for (Client *c = dwn->client_list; c != NULL; c = c->next) {
|
for (Client *c = dwn->client_list; c != NULL; c = c->next) {
|
||||||
if (c->workspace != (unsigned int)dwn->current_workspace || client_is_minimized(c)) {
|
if (c->workspace != (unsigned int)dwn->current_workspace) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool focused = (ws != NULL && ws->focused == c);
|
bool focused = (ws != NULL && ws->focused == c);
|
||||||
|
bool minimized = client_is_minimized(c);
|
||||||
|
|
||||||
unsigned long bg = focused ? colors->workspace_active : colors->panel_bg;
|
unsigned long bg;
|
||||||
|
if (minimized) {
|
||||||
|
bg = colors->workspace_inactive;
|
||||||
|
} else if (focused) {
|
||||||
|
bg = colors->workspace_active;
|
||||||
|
} else {
|
||||||
|
bg = colors->panel_bg;
|
||||||
|
}
|
||||||
XSetForeground(dpy, dwn->gc, bg);
|
XSetForeground(dpy, dwn->gc, bg);
|
||||||
XFillRectangle(dpy, panel->buffer, dwn->gc,
|
XFillRectangle(dpy, panel->buffer, dwn->gc,
|
||||||
current_x, 2, item_width - 2, panel->height - 4);
|
current_x, 2, item_width - 2, panel->height - 4);
|
||||||
|
|
||||||
char title[64];
|
char title[64];
|
||||||
strncpy(title, c->title, sizeof(title) - 4);
|
if (minimized) {
|
||||||
title[sizeof(title) - 4] = '\0';
|
snprintf(title, sizeof(title), "[%s]", c->title);
|
||||||
|
} else {
|
||||||
|
strncpy(title, c->title, sizeof(title) - 1);
|
||||||
|
title[sizeof(title) - 1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
int max_text_width = item_width - 8;
|
int max_text_width = item_width - 8;
|
||||||
bool truncated = false;
|
bool truncated = false;
|
||||||
@ -405,7 +421,14 @@ void panel_render_taskbar(Panel *panel, int x, int *width)
|
|||||||
strncat(title, "...", sizeof(title) - strlen(title) - 1);
|
strncat(title, "...", sizeof(title) - strlen(title) - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned long fg = focused ? colors->panel_bg : colors->panel_fg;
|
unsigned long fg;
|
||||||
|
if (minimized) {
|
||||||
|
fg = colors->panel_fg;
|
||||||
|
} else if (focused) {
|
||||||
|
fg = colors->panel_bg;
|
||||||
|
} else {
|
||||||
|
fg = colors->panel_fg;
|
||||||
|
}
|
||||||
panel_draw_text(panel->buffer, current_x + 4, text_y, title, strlen(title), fg);
|
panel_draw_text(panel->buffer, current_x + 4, text_y, title, strlen(title), fg);
|
||||||
|
|
||||||
current_x += item_width;
|
current_x += item_width;
|
||||||
@ -506,7 +529,11 @@ void panel_handle_click(Panel *panel, int x, int y, int button)
|
|||||||
Client *c = panel_hit_test_taskbar(panel, x, y);
|
Client *c = panel_hit_test_taskbar(panel, x, y);
|
||||||
if (c != NULL) {
|
if (c != NULL) {
|
||||||
if (button == 1) {
|
if (button == 1) {
|
||||||
client_focus(c);
|
if (client_is_minimized(c)) {
|
||||||
|
client_restore(c);
|
||||||
|
} else {
|
||||||
|
client_focus(c);
|
||||||
|
}
|
||||||
} else if (button == 3) {
|
} else if (button == 3) {
|
||||||
client_close(c);
|
client_close(c);
|
||||||
}
|
}
|
||||||
@ -542,16 +569,23 @@ Client *panel_hit_test_taskbar(Panel *panel, int x, int y)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
int taskbar_start = PANEL_PADDING + MAX_WORKSPACES * WORKSPACE_WIDTH + WIDGET_SPACING + 50;
|
Workspace *ws = workspace_get_current();
|
||||||
|
const char *layout_symbol = layout_get_symbol(ws != NULL ? ws->layout : LAYOUT_TILING);
|
||||||
|
int layout_width = panel_text_width(layout_symbol, strlen(layout_symbol));
|
||||||
|
int taskbar_start = PANEL_PADDING + MAX_WORKSPACES * WORKSPACE_WIDTH + WIDGET_SPACING + layout_width + WIDGET_SPACING;
|
||||||
if (x < taskbar_start) {
|
if (x < taskbar_start) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
int available_width = panel->width - taskbar_start - 100 - PANEL_PADDING;
|
int systray_actual_width = systray_get_width();
|
||||||
|
int ai_width = dwn->ai_enabled ? 60 : 0;
|
||||||
|
int right_reserve = systray_actual_width + ai_width + PANEL_PADDING * 2;
|
||||||
|
int available_width = panel->width - taskbar_start - right_reserve;
|
||||||
|
if (available_width < 0) available_width = 0;
|
||||||
int item_count = 0;
|
int item_count = 0;
|
||||||
|
|
||||||
for (Client *c = dwn->client_list; c != NULL; c = c->next) {
|
for (Client *c = dwn->client_list; c != NULL; c = c->next) {
|
||||||
if (c->workspace == (unsigned int)dwn->current_workspace && !client_is_minimized(c)) {
|
if (c->workspace == (unsigned int)dwn->current_workspace) {
|
||||||
item_count++;
|
item_count++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -569,7 +603,7 @@ Client *panel_hit_test_taskbar(Panel *panel, int x, int y)
|
|||||||
int i = 0;
|
int i = 0;
|
||||||
|
|
||||||
for (Client *c = dwn->client_list; c != NULL; c = c->next) {
|
for (Client *c = dwn->client_list; c != NULL; c = c->next) {
|
||||||
if (c->workspace == (unsigned int)dwn->current_workspace && !client_is_minimized(c)) {
|
if (c->workspace == (unsigned int)dwn->current_workspace) {
|
||||||
if (i == index) {
|
if (i == index) {
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
@ -615,6 +649,19 @@ void panel_toggle(Panel *panel)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void panel_raise_all(void)
|
||||||
|
{
|
||||||
|
if (dwn == NULL || dwn->display == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dwn->top_panel != NULL && dwn->top_panel->visible) {
|
||||||
|
XRaiseWindow(dwn->display, dwn->top_panel->window);
|
||||||
|
}
|
||||||
|
if (dwn->bottom_panel != NULL && dwn->bottom_panel->visible) {
|
||||||
|
XRaiseWindow(dwn->display, dwn->bottom_panel->window);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void panel_update_clock(void)
|
void panel_update_clock(void)
|
||||||
{
|
{
|
||||||
|
|||||||
391
src/systray.c
391
src/systray.c
@ -9,6 +9,7 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "notifications.h"
|
#include "notifications.h"
|
||||||
|
#include "atoms.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@ -50,6 +51,10 @@ BatteryState battery_state = {0};
|
|||||||
DropdownMenu *wifi_menu = NULL;
|
DropdownMenu *wifi_menu = NULL;
|
||||||
VolumeSlider *volume_slider = NULL;
|
VolumeSlider *volume_slider = NULL;
|
||||||
|
|
||||||
|
TrayIcon tray_icons[MAX_TRAY_ICONS];
|
||||||
|
int tray_icon_count = 0;
|
||||||
|
Window tray_selection_owner = None;
|
||||||
|
|
||||||
static pthread_t update_thread;
|
static pthread_t update_thread;
|
||||||
static pthread_mutex_t state_mutex = PTHREAD_MUTEX_INITIALIZER;
|
static pthread_mutex_t state_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||||
static volatile int thread_running = 0;
|
static volatile int thread_running = 0;
|
||||||
@ -58,6 +63,7 @@ static int systray_x = 0;
|
|||||||
static int systray_width = 0;
|
static int systray_width = 0;
|
||||||
static int wifi_icon_x = 0;
|
static int wifi_icon_x = 0;
|
||||||
static int audio_icon_x = 0;
|
static int audio_icon_x = 0;
|
||||||
|
static int xembed_icons_x = 0;
|
||||||
|
|
||||||
static void wifi_update_state_internal(void);
|
static void wifi_update_state_internal(void);
|
||||||
static void audio_update_state_internal(void);
|
static void audio_update_state_internal(void);
|
||||||
@ -854,7 +860,13 @@ int systray_get_width(void)
|
|||||||
snprintf(wifi_label, sizeof(wifi_label), "%s", wifi_icon_str);
|
snprintf(wifi_label, sizeof(wifi_label), "%s", wifi_icon_str);
|
||||||
}
|
}
|
||||||
|
|
||||||
return get_text_width(audio_label) + SYSTRAY_SPACING + get_text_width(wifi_label);
|
int builtin_width = get_text_width(audio_label) + SYSTRAY_SPACING + get_text_width(wifi_label);
|
||||||
|
int xembed_width = xembed_get_icons_width();
|
||||||
|
|
||||||
|
if (xembed_width > 0) {
|
||||||
|
return xembed_width + SYSTRAY_SPACING + builtin_width;
|
||||||
|
}
|
||||||
|
return builtin_width;
|
||||||
}
|
}
|
||||||
|
|
||||||
void systray_render(Panel *panel, int x, int *width)
|
void systray_render(Panel *panel, int x, int *width)
|
||||||
@ -885,6 +897,12 @@ void systray_render(Panel *panel, int x, int *width)
|
|||||||
systray_x = x;
|
systray_x = x;
|
||||||
int current_x = x;
|
int current_x = x;
|
||||||
|
|
||||||
|
int xembed_width = xembed_get_icons_width();
|
||||||
|
if (xembed_width > 0) {
|
||||||
|
xembed_render_icons(panel, current_x);
|
||||||
|
current_x += xembed_width + SYSTRAY_SPACING;
|
||||||
|
}
|
||||||
|
|
||||||
audio_icon_x = current_x;
|
audio_icon_x = current_x;
|
||||||
char audio_label[32];
|
char audio_label[32];
|
||||||
const char *audio_icon = (audio_snap.muted || audio_snap.volume == 0) ? ASCII_VOL_MUTE : ASCII_VOL_HIGH;
|
const char *audio_icon = (audio_snap.muted || audio_snap.volume == 0) ? ASCII_VOL_MUTE : ASCII_VOL_HIGH;
|
||||||
@ -915,6 +933,10 @@ void systray_render(Panel *panel, int x, int *width)
|
|||||||
|
|
||||||
int systray_hit_test(int x)
|
int systray_hit_test(int x)
|
||||||
{
|
{
|
||||||
|
int xembed_idx = xembed_hit_test(x);
|
||||||
|
if (xembed_idx >= 0) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
if (x >= wifi_icon_x) {
|
if (x >= wifi_icon_x) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -928,6 +950,11 @@ void systray_handle_click(int x, int y, int button)
|
|||||||
{
|
{
|
||||||
int widget = systray_hit_test(x);
|
int widget = systray_hit_test(x);
|
||||||
|
|
||||||
|
if (widget == 2) {
|
||||||
|
xembed_handle_click(x, y, button);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (widget == 0) {
|
if (widget == 0) {
|
||||||
if (volume_slider != NULL && volume_slider->visible) {
|
if (volume_slider != NULL && volume_slider->visible) {
|
||||||
volume_slider_hide(volume_slider);
|
volume_slider_hide(volume_slider);
|
||||||
@ -1034,3 +1061,365 @@ AudioState systray_get_audio_snapshot(void)
|
|||||||
pthread_mutex_unlock(&state_mutex);
|
pthread_mutex_unlock(&state_mutex);
|
||||||
return snapshot;
|
return snapshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void xembed_init(void)
|
||||||
|
{
|
||||||
|
if (dwn == NULL || dwn->display == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Display *dpy = dwn->display;
|
||||||
|
|
||||||
|
memset(tray_icons, 0, sizeof(tray_icons));
|
||||||
|
tray_icon_count = 0;
|
||||||
|
|
||||||
|
tray_selection_owner = XCreateSimpleWindow(dpy, dwn->root, -1, -1, 1, 1, 0, 0, 0);
|
||||||
|
if (tray_selection_owner == None) {
|
||||||
|
LOG_WARN("Failed to create tray selection owner window");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Window existing = XGetSelectionOwner(dpy, ewmh.NET_SYSTEM_TRAY_S0);
|
||||||
|
if (existing != None) {
|
||||||
|
LOG_WARN("Another system tray is already running");
|
||||||
|
XDestroyWindow(dpy, tray_selection_owner);
|
||||||
|
tray_selection_owner = None;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
XSetSelectionOwner(dpy, ewmh.NET_SYSTEM_TRAY_S0, tray_selection_owner, CurrentTime);
|
||||||
|
if (XGetSelectionOwner(dpy, ewmh.NET_SYSTEM_TRAY_S0) != tray_selection_owner) {
|
||||||
|
LOG_WARN("Failed to acquire system tray selection");
|
||||||
|
XDestroyWindow(dpy, tray_selection_owner);
|
||||||
|
tray_selection_owner = None;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
long orientation = 0;
|
||||||
|
XChangeProperty(dpy, tray_selection_owner, ewmh.NET_SYSTEM_TRAY_ORIENTATION,
|
||||||
|
XA_CARDINAL, 32, PropModeReplace,
|
||||||
|
(unsigned char *)&orientation, 1);
|
||||||
|
|
||||||
|
VisualID visual_id = XVisualIDFromVisual(DefaultVisual(dpy, dwn->screen));
|
||||||
|
XChangeProperty(dpy, tray_selection_owner, ewmh.NET_SYSTEM_TRAY_VISUAL,
|
||||||
|
XA_VISUALID, 32, PropModeReplace,
|
||||||
|
(unsigned char *)&visual_id, 1);
|
||||||
|
|
||||||
|
XEvent ev;
|
||||||
|
memset(&ev, 0, sizeof(ev));
|
||||||
|
ev.xclient.type = ClientMessage;
|
||||||
|
ev.xclient.window = dwn->root;
|
||||||
|
ev.xclient.message_type = ewmh.MANAGER;
|
||||||
|
ev.xclient.format = 32;
|
||||||
|
ev.xclient.data.l[0] = CurrentTime;
|
||||||
|
ev.xclient.data.l[1] = ewmh.NET_SYSTEM_TRAY_S0;
|
||||||
|
ev.xclient.data.l[2] = tray_selection_owner;
|
||||||
|
ev.xclient.data.l[3] = 0;
|
||||||
|
ev.xclient.data.l[4] = 0;
|
||||||
|
|
||||||
|
XSendEvent(dpy, dwn->root, False, StructureNotifyMask, &ev);
|
||||||
|
XFlush(dpy);
|
||||||
|
|
||||||
|
LOG_INFO("XEmbed system tray initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
void xembed_cleanup(void)
|
||||||
|
{
|
||||||
|
if (dwn == NULL || dwn->display == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < tray_icon_count; i++) {
|
||||||
|
if (tray_icons[i].window != None) {
|
||||||
|
XReparentWindow(dwn->display, tray_icons[i].window, dwn->root, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tray_icon_count = 0;
|
||||||
|
|
||||||
|
if (tray_selection_owner != None) {
|
||||||
|
XDestroyWindow(dwn->display, tray_selection_owner);
|
||||||
|
tray_selection_owner = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DEBUG("XEmbed system tray cleaned up");
|
||||||
|
}
|
||||||
|
|
||||||
|
void xembed_send_message(Window icon, long message, long detail, long data1, long data2)
|
||||||
|
{
|
||||||
|
if (dwn == NULL || dwn->display == NULL || icon == None) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
XEvent ev;
|
||||||
|
memset(&ev, 0, sizeof(ev));
|
||||||
|
ev.xclient.type = ClientMessage;
|
||||||
|
ev.xclient.window = icon;
|
||||||
|
ev.xclient.message_type = ewmh.XEMBED;
|
||||||
|
ev.xclient.format = 32;
|
||||||
|
ev.xclient.data.l[0] = CurrentTime;
|
||||||
|
ev.xclient.data.l[1] = message;
|
||||||
|
ev.xclient.data.l[2] = detail;
|
||||||
|
ev.xclient.data.l[3] = data1;
|
||||||
|
ev.xclient.data.l[4] = data2;
|
||||||
|
|
||||||
|
XSendEvent(dwn->display, icon, False, NoEventMask, &ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool xembed_get_info(Window icon, unsigned long *version, unsigned long *flags)
|
||||||
|
{
|
||||||
|
if (dwn == NULL || dwn->display == NULL || icon == None) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Atom actual_type;
|
||||||
|
int actual_format;
|
||||||
|
unsigned long nitems, bytes_after;
|
||||||
|
unsigned char *data = NULL;
|
||||||
|
|
||||||
|
if (XGetWindowProperty(dwn->display, icon, ewmh.XEMBED_INFO,
|
||||||
|
0, 2, False, ewmh.XEMBED_INFO,
|
||||||
|
&actual_type, &actual_format, &nitems, &bytes_after,
|
||||||
|
&data) != Success || data == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nitems >= 2 && actual_format == 32) {
|
||||||
|
unsigned long *info = (unsigned long *)data;
|
||||||
|
if (version != NULL) *version = info[0];
|
||||||
|
if (flags != NULL) *flags = info[1];
|
||||||
|
XFree(data);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
XFree(data);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool xembed_dock_icon(Window icon_win)
|
||||||
|
{
|
||||||
|
if (dwn == NULL || dwn->display == NULL || dwn->top_panel == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (icon_win == None) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tray_icon_count >= MAX_TRAY_ICONS) {
|
||||||
|
LOG_WARN("Maximum tray icons reached");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < tray_icon_count; i++) {
|
||||||
|
if (tray_icons[i].window == icon_win) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
XWindowAttributes wa;
|
||||||
|
if (!XGetWindowAttributes(dwn->display, icon_win, &wa)) {
|
||||||
|
LOG_WARN("Failed to get tray icon attributes");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Display *dpy = dwn->display;
|
||||||
|
|
||||||
|
XSelectInput(dpy, icon_win, StructureNotifyMask | PropertyChangeMask);
|
||||||
|
|
||||||
|
XReparentWindow(dpy, icon_win, dwn->top_panel->window, 0, 0);
|
||||||
|
|
||||||
|
XResizeWindow(dpy, icon_win, TRAY_ICON_SIZE, TRAY_ICON_SIZE);
|
||||||
|
|
||||||
|
unsigned long version = 0, flags = 0;
|
||||||
|
bool has_info = xembed_get_info(icon_win, &version, &flags);
|
||||||
|
|
||||||
|
TrayIcon *icon = &tray_icons[tray_icon_count];
|
||||||
|
icon->window = icon_win;
|
||||||
|
icon->width = TRAY_ICON_SIZE;
|
||||||
|
icon->height = TRAY_ICON_SIZE;
|
||||||
|
icon->mapped = !has_info || (flags & XEMBED_MAPPED);
|
||||||
|
icon->x = 0;
|
||||||
|
tray_icon_count++;
|
||||||
|
|
||||||
|
xembed_send_message(icon_win, XEMBED_EMBEDDED_NOTIFY, 0,
|
||||||
|
dwn->top_panel->window, XEMBED_PROTOCOL_VERSION);
|
||||||
|
|
||||||
|
if (icon->mapped) {
|
||||||
|
XMapWindow(dpy, icon_win);
|
||||||
|
}
|
||||||
|
|
||||||
|
XFlush(dpy);
|
||||||
|
|
||||||
|
LOG_INFO("Docked tray icon: window=%lu, mapped=%d", icon_win, icon->mapped);
|
||||||
|
|
||||||
|
panel_render_all();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void xembed_remove_icon(Window icon_win)
|
||||||
|
{
|
||||||
|
if (icon_win == None) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int found = -1;
|
||||||
|
for (int i = 0; i < tray_icon_count; i++) {
|
||||||
|
if (tray_icons[i].window == icon_win) {
|
||||||
|
found = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DEBUG("Removing tray icon: window=%lu", icon_win);
|
||||||
|
|
||||||
|
for (int i = found; i < tray_icon_count - 1; i++) {
|
||||||
|
tray_icons[i] = tray_icons[i + 1];
|
||||||
|
}
|
||||||
|
tray_icon_count--;
|
||||||
|
|
||||||
|
memset(&tray_icons[tray_icon_count], 0, sizeof(TrayIcon));
|
||||||
|
|
||||||
|
panel_render_all();
|
||||||
|
}
|
||||||
|
|
||||||
|
TrayIcon *xembed_find_icon(Window icon_win)
|
||||||
|
{
|
||||||
|
if (icon_win == None) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < tray_icon_count; i++) {
|
||||||
|
if (tray_icons[i].window == icon_win) {
|
||||||
|
return &tray_icons[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int xembed_get_icons_width(void)
|
||||||
|
{
|
||||||
|
if (tray_icon_count == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return tray_icon_count * (TRAY_ICON_SIZE + TRAY_ICON_SPACING);
|
||||||
|
}
|
||||||
|
|
||||||
|
void xembed_render_icons(Panel *panel, int x)
|
||||||
|
{
|
||||||
|
if (panel == NULL || dwn == NULL || dwn->display == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Display *dpy = dwn->display;
|
||||||
|
int current_x = x;
|
||||||
|
int y = (panel->height - TRAY_ICON_SIZE) / 2;
|
||||||
|
|
||||||
|
xembed_icons_x = x;
|
||||||
|
|
||||||
|
for (int i = 0; i < tray_icon_count; i++) {
|
||||||
|
TrayIcon *icon = &tray_icons[i];
|
||||||
|
if (icon->window == None) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
icon->x = current_x;
|
||||||
|
|
||||||
|
if (icon->mapped) {
|
||||||
|
XMoveResizeWindow(dpy, icon->window, current_x, y,
|
||||||
|
TRAY_ICON_SIZE, TRAY_ICON_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
current_x += TRAY_ICON_SIZE + TRAY_ICON_SPACING;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int xembed_hit_test(int x)
|
||||||
|
{
|
||||||
|
if (tray_icon_count == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < tray_icon_count; i++) {
|
||||||
|
TrayIcon *icon = &tray_icons[i];
|
||||||
|
if (x >= icon->x && x < icon->x + TRAY_ICON_SIZE) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void xembed_handle_click(int x, int y, int button)
|
||||||
|
{
|
||||||
|
if (dwn == NULL || dwn->display == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int index = xembed_hit_test(x);
|
||||||
|
if (index < 0 || index >= tray_icon_count) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TrayIcon *icon = &tray_icons[index];
|
||||||
|
if (icon->window == None || !icon->mapped) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int icon_x = x - icon->x;
|
||||||
|
int icon_y = y;
|
||||||
|
|
||||||
|
XEvent ev;
|
||||||
|
memset(&ev, 0, sizeof(ev));
|
||||||
|
ev.xbutton.type = ButtonPress;
|
||||||
|
ev.xbutton.window = icon->window;
|
||||||
|
ev.xbutton.root = dwn->root;
|
||||||
|
ev.xbutton.subwindow = None;
|
||||||
|
ev.xbutton.time = CurrentTime;
|
||||||
|
ev.xbutton.x = icon_x;
|
||||||
|
ev.xbutton.y = icon_y;
|
||||||
|
ev.xbutton.x_root = x;
|
||||||
|
ev.xbutton.y_root = y;
|
||||||
|
ev.xbutton.state = 0;
|
||||||
|
ev.xbutton.button = button;
|
||||||
|
ev.xbutton.same_screen = True;
|
||||||
|
|
||||||
|
XSendEvent(dwn->display, icon->window, False, ButtonPressMask, &ev);
|
||||||
|
|
||||||
|
ev.xbutton.type = ButtonRelease;
|
||||||
|
XSendEvent(dwn->display, icon->window, False, ButtonReleaseMask, &ev);
|
||||||
|
|
||||||
|
XFlush(dwn->display);
|
||||||
|
}
|
||||||
|
|
||||||
|
void xembed_update_icon_state(Window icon_win)
|
||||||
|
{
|
||||||
|
TrayIcon *icon = xembed_find_icon(icon_win);
|
||||||
|
if (icon == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long version = 0, flags = 0;
|
||||||
|
if (!xembed_get_info(icon_win, &version, &flags)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool should_map = (flags & XEMBED_MAPPED) != 0;
|
||||||
|
if (should_map != icon->mapped) {
|
||||||
|
icon->mapped = should_map;
|
||||||
|
if (should_map) {
|
||||||
|
XMapWindow(dwn->display, icon_win);
|
||||||
|
} else {
|
||||||
|
XUnmapWindow(dwn->display, icon_win);
|
||||||
|
}
|
||||||
|
panel_render_all();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -10,6 +10,7 @@
|
|||||||
#include "atoms.h"
|
#include "atoms.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "panel.h"
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@ -106,6 +107,8 @@ void workspace_switch(int index)
|
|||||||
|
|
||||||
atoms_set_current_desktop(index);
|
atoms_set_current_desktop(index);
|
||||||
|
|
||||||
|
panel_raise_all();
|
||||||
|
|
||||||
XUngrabServer(dwn->display);
|
XUngrabServer(dwn->display);
|
||||||
XSync(dwn->display, False);
|
XSync(dwn->display, False);
|
||||||
|
|
||||||
@ -324,6 +327,7 @@ void workspace_arrange_current(void)
|
|||||||
{
|
{
|
||||||
XGrabServer(dwn->display);
|
XGrabServer(dwn->display);
|
||||||
workspace_arrange(dwn->current_workspace);
|
workspace_arrange(dwn->current_workspace);
|
||||||
|
panel_raise_all();
|
||||||
XUngrabServer(dwn->display);
|
XUngrabServer(dwn->display);
|
||||||
XSync(dwn->display, False);
|
XSync(dwn->display, False);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user