diff --git a/deskbar/src/deskbar.c b/deskbar/src/deskbar.c index 0a6be61..350f464 100644 --- a/deskbar/src/deskbar.c +++ b/deskbar/src/deskbar.c @@ -453,7 +453,7 @@ static const char *S0_LBL[] = { "Media Player","Image Viewer","OnlyOffice","PDF Viewer" }; static const char *S0_CMD[] = { - "firefox &","xterm &","thunar &","mousepad &", + "firefox &","alacritty &","thunar &","mousepad &", "mpv --player-operation-mode=pseudo-gui &","viewnior &", "onlyoffice-desktopeditors &","evince &" }; @@ -471,12 +471,12 @@ static const char *S1_CMD[] = { #define NS1 5 static const MI MENU[] = { - { "Install SkyOS...", "xterm -e 'sudo skyinstall; exec bash' &", 0, 0,-1 }, + { "Install SkyOS...", "alacritty -e bash -c 'sudo skyinstall; exec bash' &", 0, 0,-1 }, { NULL, NULL, 1, 0,-1 }, { "Applications", NULL, 0, 1, 0 }, { "Settings", NULL, 0, 1, 1 }, { NULL, NULL, 1, 0,-1 }, - { "About SkyOS", "xterm -e 'fastfetch; read' &", 0, 0,-1 }, + { "About SkyOS", "alacritty -e bash -c 'fastfetch; read' &", 0, 0,-1 }, { "Lock Screen", "xscreensaver-command -lock &", 0, 0,-1 }, { NULL, NULL, 1, 0,-1 }, { "Restart", "systemctl reboot", 0, 0,-1 }, diff --git a/iso/docker/entrypoint.sh b/iso/docker/entrypoint.sh index 0fa9e1d..8aba51d 100755 --- a/iso/docker/entrypoint.sh +++ b/iso/docker/entrypoint.sh @@ -32,6 +32,7 @@ xorg-xrandr xorg-xset xorg-xsetroot xterm +alacritty ttf-dejavu ttf-liberation noto-fonts @@ -420,6 +421,59 @@ URxvt*background: #0E1520 URxvt*foreground: #C8D4E8 XRES +mkdir -p "$SKEL/.config/alacritty" +cat > "$SKEL/.config/alacritty/alacritty.toml" <<'ALACRITTY' +[window] +padding = { x = 10, y = 8 } +decorations = "none" +opacity = 0.96 +title = "Terminal" + +[font] +normal = { family = "DejaVu Sans Mono", style = "Regular" } +bold = { family = "DejaVu Sans Mono", style = "Bold" } +size = 11.0 + +[colors.primary] +background = "#0E1520" +foreground = "#C8D4E8" + +[colors.cursor] +text = "#0E1520" +cursor = "#4A90D9" + +[colors.normal] +black = "#1A1E2A" +red = "#CC4444" +green = "#44AA66" +yellow = "#BBAA22" +blue = "#4488CC" +magenta = "#8844CC" +cyan = "#44AACC" +white = "#C8D4E8" + +[colors.bright] +black = "#3A4050" +red = "#FF6666" +green = "#66CC88" +yellow = "#DDCC44" +blue = "#66AAFF" +magenta = "#AA66FF" +cyan = "#66CCEE" +white = "#EEF4FF" + +[colors.selection] +background = "#2060A0" +text = "#F0F4FF" + +[scrolling] +history = 10000 + +[cursor] +style = { shape = "Block", blinking = "On" } +blink_interval = 500 +ALACRITTY + mkdir -p "$SKEL/.config/dunst" cat > "$SKEL/.config/dunst/dunstrc" <<'DUNST' [global] diff --git a/themes/SkyOS/gtk-2.0/gtkrc b/themes/SkyOS/gtk-2.0/gtkrc index 74c0023..c336629 100644 --- a/themes/SkyOS/gtk-2.0/gtkrc +++ b/themes/SkyOS/gtk-2.0/gtkrc @@ -1,32 +1,31 @@ -# SkyOS GTK2 Theme — BeOS/SkyOS tan palette -# Covers legacy GTK2 apps (GIMP, older system tools) +# SkyOS GTK2 Theme — dark navy palette (legacy GTK2 apps) -gtk-color-scheme = "bg_color:#D8CEAA\nfg_color:#100800\nbase_color:#F5F0E0\ntext_color:#100800\nselected_bg_color:#4A6888\nselected_fg_color:#FFFFFF\ntooltip_bg_color:#FFFCE0\ntooltip_fg_color:#100800" +gtk-color-scheme = "bg_color:#1E2436\nfg_color:#D8E4F4\nbase_color:#0E1424\ntext_color:#D8E4F4\nselected_bg_color:#1E4D78\nselected_fg_color:#F0F6FF\ntooltip_bg_color:#0C1020\ntooltip_fg_color:#D8E4F4" style "default" { bg[NORMAL] = @bg_color - bg[PRELIGHT] = "#E8DEC0" - bg[ACTIVE] = "#B0A888" + bg[PRELIGHT] = "#283650" + bg[ACTIVE] = "#181E2E" bg[SELECTED] = @selected_bg_color - bg[INSENSITIVE] = @bg_color + bg[INSENSITIVE] = "#161C2A" fg[NORMAL] = @fg_color fg[PRELIGHT] = @fg_color fg[ACTIVE] = @fg_color fg[SELECTED] = @selected_fg_color - fg[INSENSITIVE] = "#7A7060" + fg[INSENSITIVE] = "#48587A" base[NORMAL] = @base_color - base[PRELIGHT] = @base_color - base[ACTIVE] = "#D0C8A8" + base[PRELIGHT] = "#141C2A" + base[ACTIVE] = "#0A101E" base[SELECTED] = @selected_bg_color - base[INSENSITIVE] = @bg_color + base[INSENSITIVE] = "#161C2A" text[NORMAL] = @text_color text[PRELIGHT] = @text_color text[ACTIVE] = @text_color text[SELECTED] = @selected_fg_color - text[INSENSITIVE] = "#7A7060" + text[INSENSITIVE] = "#48587A" GtkWidget::focus-line-width = 1 GtkButton::default-border = { 1, 1, 1, 1 } @@ -35,17 +34,17 @@ style "default" { class "GtkWidget" style "default" style "menubar" { - bg[NORMAL] = "#C8BE9A" - bg[PRELIGHT] = "#4A6888" - fg[PRELIGHT] = "#FFFFFF" + bg[NORMAL] = "#161C2A" + bg[PRELIGHT] = "#1E4D78" + fg[PRELIGHT] = "#F0F6FF" } class "GtkMenuBar" style "menubar" style "menu" { - bg[NORMAL] = "#D8CEAA" - bg[PRELIGHT] = "#4A6888" - fg[NORMAL] = "#100800" - fg[PRELIGHT] = "#FFFFFF" + bg[NORMAL] = "#161C2A" + bg[PRELIGHT] = "#1E4D78" + fg[NORMAL] = "#D8E4F4" + fg[PRELIGHT] = "#F0F6FF" } class "GtkMenu" style "menu" class "GtkMenuItem" style "menu" @@ -60,13 +59,14 @@ style "entry" { bg[NORMAL] = @base_color bg[SELECTED] = @selected_bg_color fg[NORMAL] = @text_color + text[NORMAL] = @text_color } class "GtkEntry" style "entry" -gtk-theme-name = "SkyOS" -gtk-icon-theme-name = "Adwaita" -gtk-font-name = "Sans 10" +gtk-theme-name = "SkyOS" +gtk-icon-theme-name = "Adwaita" +gtk-font-name = "Sans 10" gtk-cursor-theme-name = "DMZ-White" -gtk-button-images = 0 -gtk-menu-images = 0 -gtk-toolbar-style = GTK_TOOLBAR_ICONS +gtk-button-images = 0 +gtk-menu-images = 0 +gtk-toolbar-style = GTK_TOOLBAR_ICONS diff --git a/themes/SkyOS/gtk-3.0/gtk.css b/themes/SkyOS/gtk-3.0/gtk.css index 5b6fc12..e8d666b 100644 --- a/themes/SkyOS/gtk-3.0/gtk.css +++ b/themes/SkyOS/gtk-3.0/gtk.css @@ -1,25 +1,24 @@ -/* SkyOS GTK3 Theme — BeOS/SkyOS tan palette */ +/* SkyOS GTK3 Theme — dark navy palette matching skywm/skydeskbar */ -/* ── variables ───────────────────────────────────────────────────────── */ -@define-color bg_color #D8CEAA; -@define-color bg_dark #C8BE9A; -@define-color bg_darker #B0A888; -@define-color bg_light #E8DEC0; -@define-color border_light #F0E8D0; -@define-color border_dark #6B5F50; -@define-color text_color #100800; -@define-color text_disabled #7A7060; -@define-color selected_bg #4A6888; -@define-color selected_fg #FFFFFF; -@define-color accent #4A6888; -@define-color button_bg #C8BE9A; -@define-color button_hover #D8D0B8; -@define-color button_active #A89E88; -@define-color entry_bg #F5F0E0; -@define-color tooltip_bg #FFFCE0; -@define-color link_color #2244AA; +@define-color bg_color #1E2436; +@define-color bg_dark #161C2A; +@define-color bg_darker #0E1220; +@define-color bg_light #262E42; +@define-color border_light #3A6898; +@define-color border_dark #080C18; +@define-color text_color #D8E4F4; +@define-color text_disabled #48587A; +@define-color selected_bg #1E4D78; +@define-color selected_fg #F0F6FF; +@define-color accent #3A6898; +@define-color button_bg #202A3C; +@define-color button_hover #283650; +@define-color button_active #181E2E; +@define-color entry_bg #0E1424; +@define-color tooltip_bg #0C1020; +@define-color link_color #5088CC; -/* ── window / base ───────────────────────────────────────────────────── */ +/* ── base ─────────────────────────────────────────────────────────── */ * { font-family: "Sans"; font-size: 10pt; @@ -33,7 +32,7 @@ window, .background { color: @text_color; } -/* ── header bars ─────────────────────────────────────────────────────── */ +/* ── header bars ─────────────────────────────────────────────────── */ headerbar, .titlebar { background: linear-gradient(to bottom, @bg_light, @bg_dark); border-bottom: 1px solid @border_dark; @@ -43,41 +42,42 @@ headerbar, .titlebar { } headerbar .title { font-weight: bold; } -/* ── buttons ─────────────────────────────────────────────────────────── */ +/* ── buttons ─────────────────────────────────────────────────────── */ button { - background: linear-gradient(to bottom, @border_light, @button_bg); + background: linear-gradient(to bottom, @bg_light, @button_bg); border: 1px solid @border_dark; border-radius: 2px; padding: 3px 10px; color: @text_color; - box-shadow: inset 1px 1px 0 @border_light, inset -1px -1px 0 @border_dark; + box-shadow: inset 0 1px 0 rgba(255,255,255,0.06), inset 0 -1px 0 rgba(0,0,0,0.3); } button:hover { - background: linear-gradient(to bottom, #F8F0D8, @button_hover); + background: linear-gradient(to bottom, @button_hover, @bg_light); + border-color: @border_light; } button:active, button:checked { - background: linear-gradient(to bottom, @button_active, @bg_darker); - box-shadow: inset 1px 1px 0 @border_dark, inset -1px -1px 0 @border_light; + background: linear-gradient(to bottom, @button_active, @bg_dark); + box-shadow: inset 0 1px 0 rgba(0,0,0,0.3); } button:disabled { color: @text_disabled; background: @bg_color; } button.suggested-action { - background: linear-gradient(to bottom, #5A7898, @accent); - color: white; - border-color: #3A5878; + background: linear-gradient(to bottom, #2A5E88, @selected_bg); + color: @selected_fg; + border-color: #1A3E58; } button.destructive-action { - background: linear-gradient(to bottom, #DD5555, #BB3333); + background: linear-gradient(to bottom, #CC4444, #AA2222); color: white; - border-color: #993333; + border-color: #882222; } -/* ── menus ───────────────────────────────────────────────────────────── */ +/* ── menus ───────────────────────────────────────────────────────── */ menu, .menu, .context-menu { - background-color: @bg_color; - border: 1px solid @border_dark; + background-color: @bg_dark; + border: 1px solid @border_light; padding: 2px 0; } menuitem { @@ -96,22 +96,22 @@ separator, menuitem > separator { margin: 3px 6px; } -/* ── entries (text input) ────────────────────────────────────────────── */ +/* ── text inputs ─────────────────────────────────────────────────── */ entry { background-color: @entry_bg; border: 1px solid @border_dark; border-radius: 1px; padding: 3px 6px; color: @text_color; - box-shadow: inset 1px 1px 2px rgba(0,0,0,0.12); + box-shadow: inset 0 1px 2px rgba(0,0,0,0.4); } entry:focus { border-color: @accent; - box-shadow: inset 1px 1px 2px rgba(0,0,0,0.12), 0 0 0 2px rgba(74,104,136,0.25); + box-shadow: inset 0 1px 2px rgba(0,0,0,0.4), 0 0 0 2px rgba(58,104,152,0.35); } -entry:disabled { background-color: @bg_color; color: @text_disabled; } +entry:disabled { background-color: @bg_darker; color: @text_disabled; } -/* ── lists & trees ───────────────────────────────────────────────────── */ +/* ── lists & trees ───────────────────────────────────────────────── */ treeview, listview, .view { background-color: @entry_bg; color: @text_color; @@ -127,8 +127,9 @@ treeview header button { border-radius: 0; box-shadow: none; } +treeview.odd-row { background-color: @bg_darker; } -/* ── notebooks (tabs) ────────────────────────────────────────────────── */ +/* ── notebooks (tabs) ────────────────────────────────────────────── */ notebook > header { background-color: @bg_darker; border-bottom: 1px solid @border_dark; @@ -140,15 +141,17 @@ notebook > header tab { padding: 4px 12px; margin-right: 2px; border-radius: 2px 2px 0 0; + color: @text_disabled; } notebook > header tab:checked { background-color: @bg_color; border-bottom: 1px solid @bg_color; margin-bottom: -1px; + color: @text_color; } notebook > stack { background-color: @bg_color; } -/* ── scrollbars ──────────────────────────────────────────────────────── */ +/* ── scrollbars ──────────────────────────────────────────────────── */ scrollbar { background-color: @bg_darker; border: 1px solid @border_dark; @@ -160,45 +163,46 @@ scrollbar slider { min-width: 12px; min-height: 12px; } -scrollbar slider:hover { background-color: @button_hover; } -scrollbar button { background-color: @bg_dark; } +scrollbar slider:hover { background-color: @button_hover; border-color: @border_light; } +scrollbar button { background-color: @bg_dark; color: @text_color; } -/* ── toolbars ────────────────────────────────────────────────────────── */ +/* ── toolbars ────────────────────────────────────────────────────── */ toolbar, .toolbar { background: linear-gradient(to bottom, @bg_light, @bg_dark); border-bottom: 1px solid @border_dark; padding: 2px; } -/* ── statusbar ───────────────────────────────────────────────────────── */ +/* ── statusbar ───────────────────────────────────────────────────── */ statusbar { background-color: @bg_darker; border-top: 1px solid @border_dark; padding: 2px 6px; font-size: 9pt; + color: @text_disabled; } -/* ── tooltips ────────────────────────────────────────────────────────── */ +/* ── tooltips ────────────────────────────────────────────────────── */ tooltip { background-color: @tooltip_bg; - border: 1px solid @border_dark; + border: 1px solid @border_light; color: @text_color; padding: 4px 8px; border-radius: 0; } -/* ── progress bars ───────────────────────────────────────────────────── */ +/* ── progress bars ───────────────────────────────────────────────── */ progressbar trough { background-color: @bg_darker; border: 1px solid @border_dark; border-radius: 1px; } progressbar progress { - background-color: @accent; + background: linear-gradient(to right, @selected_bg, @accent); border-radius: 1px; } -/* ── checkboxes & radio buttons ──────────────────────────────────────── */ +/* ── checkboxes & radio buttons ──────────────────────────────────── */ checkbutton check, radiobutton radio { background-color: @entry_bg; border: 1px solid @border_dark; @@ -206,24 +210,32 @@ checkbutton check, radiobutton radio { min-height: 14px; } checkbutton check:checked { - background-color: @accent; - color: white; + background-color: @selected_bg; + border-color: @accent; + color: @selected_fg; } -radiobutton radio:checked { background-color: @accent; } +radiobutton radio:checked { background-color: @selected_bg; border-color: @accent; } -/* ── spin buttons ────────────────────────────────────────────────────── */ +/* ── spin buttons ────────────────────────────────────────────────── */ spinbutton { background-color: @entry_bg; border: 1px solid @border_dark; } -spinbutton button { border-radius: 0; padding: 0 4px; } +spinbutton button { border-radius: 0; padding: 0 4px; background: @bg_dark; } -/* ── combo boxes ─────────────────────────────────────────────────────── */ +/* ── combo boxes ─────────────────────────────────────────────────── */ combobox button { - background: linear-gradient(to bottom, @border_light, @button_bg); + background: linear-gradient(to bottom, @bg_light, @button_bg); } -/* ── file chooser ────────────────────────────────────────────────────── */ -.sidebar { background-color: @bg_darker; } +/* ── sidebar (file chooser) ──────────────────────────────────────── */ +.sidebar { background-color: @bg_darker; border-right: 1px solid @border_dark; } +.sidebar row { color: @text_color; } .sidebar row:selected { background-color: @selected_bg; color: @selected_fg; } -/* ── links ───────────────────────────────────────────────────────────── */ -*:link { color: @link_color; } -*:visited { color: #6644AA; } +/* ── links ───────────────────────────────────────────────────────── */ +*:link { color: @link_color; } +*:visited { color: #8860CC; } + +/* ── infobar / message bar ───────────────────────────────────────── */ +infobar { background-color: @bg_light; border-bottom: 1px solid @border_dark; } + +/* ── Thunar-specific tweaks ──────────────────────────────────────── */ +.thunar .pathbar button { border-radius: 2px; margin: 1px; } diff --git a/wm/Makefile b/wm/Makefile index 71eb493..055d2cf 100644 --- a/wm/Makefile +++ b/wm/Makefile @@ -14,7 +14,7 @@ endif CFLAGS = -Wall -Wextra -O2 -Iinclude $(PKG_XFT_C) LDFLAGS = $(PKG_XFT_L) -SRC = src/wm.c src/client.c src/events.c src/draw.c src/color.c src/menu.c +SRC = src/wm.c src/client.c src/events.c src/draw.c src/color.c src/menu.c src/switcher.c OBJ = $(SRC:.c=.o) BIN = skywm diff --git a/wm/include/wm.h b/wm/include/wm.h index 0b1abfd..9cf7fee 100644 --- a/wm/include/wm.h +++ b/wm/include/wm.h @@ -121,6 +121,14 @@ typedef struct WM { int menu_visible; int menu_x, menu_y; int menu_hovered; + + /* Alt+Tab visual switcher */ + Window swl_win; + int swl_visible; + int swl_idx; + int swl_count; + Window swl_wins[64]; + char swl_titles[64][128]; } WM; /* core */ @@ -163,6 +171,12 @@ void menu_draw(WM *wm); void menu_click(WM *wm, int px, int py); int menu_item_at(int py); +/* switcher */ +void switcher_show(WM *wm); +void switcher_next(WM *wm); +void switcher_hide(WM *wm, int commit); +void switcher_draw(WM *wm); + /* events */ void on_map_request(WM *wm, XMapRequestEvent *e); void on_unmap_notify(WM *wm, XUnmapEvent *e); @@ -176,5 +190,6 @@ void on_key_press(WM *wm, XKeyEvent *e); void on_enter_notify(WM *wm, XCrossingEvent *e); void on_client_message(WM *wm, XClientMessageEvent *e); void on_property_notify(WM *wm, XPropertyEvent *e); +void on_key_release(WM *wm, XKeyEvent *e); #endif diff --git a/wm/src/events.c b/wm/src/events.c index 56271a4..acf4909 100644 --- a/wm/src/events.c +++ b/wm/src/events.c @@ -66,6 +66,10 @@ void on_expose(WM *wm, XExposeEvent *e) { menu_draw(wm); return; } + if (wm->swl_visible && e->window == wm->swl_win) { + switcher_draw(wm); + return; + } Client *c = client_find_by_frame(wm, e->window); if (c && !c->minimized) frame_draw(wm, c); } @@ -259,13 +263,17 @@ void on_key_press(WM *wm, XKeyEvent *e) { if ((e->state & Mod1Mask) && sym == XK_F4 && wm->focused) send_delete(wm, wm->focused->win); - /* Alt+Tab — cycle windows */ - if ((e->state & Mod1Mask) && sym == XK_Tab && wm->clients) { - Client *start = wm->focused ? wm->focused->next : wm->clients; - Client *n = start; - while (n && n->minimized) n = n->next; - if (!n) { n = wm->clients; while (n && n->minimized) n = n->next; } - if (n && n != wm->focused) client_focus(wm, n); + /* Alt+Tab — visual switcher */ + if ((e->state & Mod1Mask) && sym == XK_Tab) { + if (wm->swl_visible) switcher_next(wm); + else if (wm->clients) switcher_show(wm); + return; + } + + /* While switcher is visible, eat all other keys until Alt released */ + if (wm->swl_visible) { + if (sym == XK_Escape) switcher_hide(wm, 0); + return; } /* Alt+F9 — minimize */ @@ -300,3 +308,11 @@ void on_key_press(WM *wm, XKeyEvent *e) { if (sym == XK_Escape && wm->menu_visible) menu_hide(wm); } + +void on_key_release(WM *wm, XKeyEvent *e) { + if (!wm->swl_visible) return; + KeySym sym = XLookupKeysym(e, 0); + if (sym == XK_Alt_L || sym == XK_Alt_R || + sym == XK_Meta_L || sym == XK_Meta_R) + switcher_hide(wm, 1); +} diff --git a/wm/src/menu.c b/wm/src/menu.c index d402cce..8c3d07f 100644 --- a/wm/src/menu.c +++ b/wm/src/menu.c @@ -6,12 +6,12 @@ static const MenuItem MENU_ITEMS[] = { { "App Launcher", "skylauncher &" }, - { "Terminal", "xterm &" }, + { "Terminal", "alacritty &" }, { "File Manager", "thunar &" }, { "Text Editor", "mousepad &" }, { "──────────────────", NULL }, { "Web Browser", "firefox &" }, - { "System Monitor", "xterm -T 'System Monitor' -e htop &" }, + { "System Monitor", "alacritty -T 'System Monitor' -e htop &" }, { "Volume Control", "pavucontrol &" }, { "Display Settings", "arandr &" }, { "──────────────────", NULL }, diff --git a/wm/src/switcher.c b/wm/src/switcher.c new file mode 100644 index 0000000..45cb2d3 --- /dev/null +++ b/wm/src/switcher.c @@ -0,0 +1,179 @@ +#include "wm.h" + +#define SWL_ITEM_W 130 +#define SWL_ITEM_H 68 +#define SWL_ICON_SZ 34 +#define SWL_PAD 12 +#define SWL_MAX_VIS 8 + +static void swl_populate(WM *wm) { + wm->swl_count = 0; + /* current focused window goes first */ + if (wm->focused && !wm->focused->minimized) { + wm->swl_wins[0] = wm->focused->win; + strncpy(wm->swl_titles[0], wm->focused->title, 127); + wm->swl_titles[0][127] = '\0'; + wm->swl_count = 1; + } + for (Client *c = wm->clients; c && wm->swl_count < 64; c = c->next) { + if (c->minimized) continue; + if (wm->focused && c == wm->focused) continue; + wm->swl_wins[wm->swl_count] = c->win; + strncpy(wm->swl_titles[wm->swl_count], c->title, 127); + wm->swl_titles[wm->swl_count][127] = '\0'; + wm->swl_count++; + } +} + +static int swl_vis(WM *wm) { + return wm->swl_count < SWL_MAX_VIS ? wm->swl_count : SWL_MAX_VIS; +} + +/* Scroll window that keeps the selected item centered */ +static int swl_view_start(WM *wm) { + int vis = swl_vis(wm); + if (wm->swl_count <= SWL_MAX_VIS) return 0; + int start = wm->swl_idx - vis / 2; + if (start < 0) start = 0; + if (start + vis > wm->swl_count) start = wm->swl_count - vis; + return start; +} + +void switcher_draw(WM *wm) { + if (!wm->swl_visible || !wm->swl_win) return; + + int vis = swl_vis(wm); + int vstart = swl_view_start(wm); + int tw = vis * SWL_ITEM_W + (vis + 1) * SWL_PAD; + int th = SWL_ITEM_H + SWL_PAD * 2; + + XSetForeground(wm->dpy, wm->gc, color_alloc(wm, 0x141A28)); + XFillRectangle(wm->dpy, wm->swl_win, wm->gc, 0, 0, tw, th); + + XSetForeground(wm->dpy, wm->gc, color_alloc(wm, COLOR_BORDER_LIGHT)); + XDrawRectangle(wm->dpy, wm->swl_win, wm->gc, 0, 0, tw - 1, th - 1); + XSetForeground(wm->dpy, wm->gc, color_alloc(wm, COLOR_BORDER_DARK)); + XDrawRectangle(wm->dpy, wm->swl_win, wm->gc, 1, 1, tw - 3, th - 3); + + XftDraw *xd = XftDrawCreate(wm->dpy, wm->swl_win, wm->visual, wm->cmap); + if (!xd) return; + + for (int vi = 0; vi < vis; vi++) { + int i = vstart + vi; + int selected = (i == wm->swl_idx); + int ix = SWL_PAD + vi * (SWL_ITEM_W + SWL_PAD); + int iy = SWL_PAD; + + unsigned long card_bg = color_alloc(wm, selected ? COLOR_TITLE_ACTIVE : 0x1C2338); + XSetForeground(wm->dpy, wm->gc, card_bg); + XFillRectangle(wm->dpy, wm->swl_win, wm->gc, ix, iy, SWL_ITEM_W, SWL_ITEM_H); + + XSetForeground(wm->dpy, wm->gc, + color_alloc(wm, selected ? COLOR_BORDER_LIGHT : 0x242C3E)); + XDrawRectangle(wm->dpy, wm->swl_win, wm->gc, ix, iy, SWL_ITEM_W - 1, SWL_ITEM_H - 1); + + /* letter icon */ + int icon_x = ix + (SWL_ITEM_W - SWL_ICON_SZ) / 2; + int icon_y = iy + 8; + XSetForeground(wm->dpy, wm->gc, + color_alloc(wm, selected ? 0x285898 : 0x182538)); + XFillRectangle(wm->dpy, wm->swl_win, wm->gc, icon_x, icon_y, SWL_ICON_SZ, SWL_ICON_SZ); + + char letter[2] = { wm->swl_titles[i][0] ? wm->swl_titles[i][0] : '?', '\0' }; + if (letter[0] >= 'a' && letter[0] <= 'z') letter[0] -= 32; + + XftColor icon_col; + xft_color(wm, selected ? COLOR_TITLE_TEXT_ACT : COLOR_TITLE_TEXT_INACT, &icon_col); + XGlyphInfo ext; + XftTextExtentsUtf8(wm->dpy, wm->font_title, (const FcChar8 *)letter, 1, &ext); + int lx = icon_x + (SWL_ICON_SZ - (int)ext.width) / 2; + int ly = icon_y + (SWL_ICON_SZ + (int)ext.height) / 2 - ext.y; + XftDrawStringUtf8(xd, &icon_col, wm->font_title, lx, ly, (const FcChar8 *)letter, 1); + XftColorFree(wm->dpy, wm->visual, wm->cmap, &icon_col); + + /* title below icon */ + const char *title = wm->swl_titles[i]; + XftColor title_col; + xft_color(wm, selected ? COLOR_TITLE_TEXT_ACT : COLOR_TITLE_TEXT_INACT, &title_col); + XGlyphInfo text_ext; + XftTextExtentsUtf8(wm->dpy, wm->font_title, + (const FcChar8 *)title, strlen(title), &text_ext); + int tx = ix + (SWL_ITEM_W - (int)text_ext.width) / 2; + if (tx < ix + 4) tx = ix + 4; + int ty = icon_y + SWL_ICON_SZ + 4 + (int)text_ext.height - text_ext.y; + XRectangle clip = { (short)(ix + 2), (short)(icon_y + SWL_ICON_SZ + 4), + (unsigned short)(SWL_ITEM_W - 4), 14 }; + XftDrawSetClipRectangles(xd, 0, 0, &clip, 1); + XftDrawStringUtf8(xd, &title_col, wm->font_title, tx, ty, + (const FcChar8 *)title, strlen(title)); + XftDrawSetClip(xd, None); + XftColorFree(wm->dpy, wm->visual, wm->cmap, &title_col); + } + + /* scroll arrows when more windows than fit */ + if (wm->swl_count > SWL_MAX_VIS) { + XftColor arrow; + xft_color(wm, 0x5878A0, &arrow); + if (vstart > 0) + XftDrawStringUtf8(xd, &arrow, wm->font_title, 3, th / 2 + 4, + (const FcChar8 *)"\xe2\x97\x80", 3); + if (vstart + SWL_MAX_VIS < wm->swl_count) + XftDrawStringUtf8(xd, &arrow, wm->font_title, tw - 11, th / 2 + 4, + (const FcChar8 *)"\xe2\x96\xb6", 3); + XftColorFree(wm->dpy, wm->visual, wm->cmap, &arrow); + } + + XftDrawDestroy(xd); + XFlush(wm->dpy); +} + +void switcher_show(WM *wm) { + swl_populate(wm); + if (wm->swl_count < 2) return; + + wm->swl_idx = 1; + wm->swl_visible = 1; + + int vis = swl_vis(wm); + int tw = vis * SWL_ITEM_W + (vis + 1) * SWL_PAD; + int th = SWL_ITEM_H + SWL_PAD * 2; + int sw = DisplayWidth(wm->dpy, wm->screen); + int sh = DisplayHeight(wm->dpy, wm->screen); + + XSetWindowAttributes swa; + swa.override_redirect = True; + swa.background_pixel = color_alloc(wm, 0x141A28); + swa.event_mask = ExposureMask; + wm->swl_win = XCreateWindow( + wm->dpy, wm->root, + (sw - tw) / 2, (sh - th) / 2, tw, th, 2, + CopyFromParent, InputOutput, CopyFromParent, + CWOverrideRedirect | CWBackPixel | CWEventMask, &swa + ); + XSetWindowBorder(wm->dpy, wm->swl_win, color_alloc(wm, COLOR_BORDER_LIGHT)); + XMapRaised(wm->dpy, wm->swl_win); + switcher_draw(wm); + + XGrabKeyboard(wm->dpy, wm->root, False, GrabModeAsync, GrabModeAsync, CurrentTime); +} + +void switcher_next(WM *wm) { + if (!wm->swl_visible) return; + wm->swl_idx = (wm->swl_idx + 1) % wm->swl_count; + switcher_draw(wm); +} + +void switcher_hide(WM *wm, int commit) { + if (!wm->swl_visible) return; + XUngrabKeyboard(wm->dpy, CurrentTime); + if (wm->swl_win) { XDestroyWindow(wm->dpy, wm->swl_win); wm->swl_win = 0; } + wm->swl_visible = 0; + + if (commit && wm->swl_count > 0) { + Client *c = client_find_by_win(wm, wm->swl_wins[wm->swl_idx]); + if (c) { + if (c->minimized) client_restore(wm, c); + else client_focus(wm, c); + } + } +} diff --git a/wm/src/wm.c b/wm/src/wm.c index b54c132..5094066 100644 --- a/wm/src/wm.c +++ b/wm/src/wm.c @@ -79,7 +79,7 @@ void wm_init(WM *wm) { XSetErrorHandler(x_error_start); XSelectInput(wm->dpy, wm->root, SubstructureRedirectMask | SubstructureNotifyMask | - KeyPressMask | ButtonPressMask); + KeyPressMask | KeyReleaseMask | ButtonPressMask); XSync(wm->dpy, False); XSetErrorHandler(x_error_handler); @@ -138,6 +138,7 @@ void wm_run(WM *wm) { case ButtonRelease: on_button_release(wm, &e.xbutton); break; case MotionNotify: on_motion_notify(wm, &e.xmotion); break; case KeyPress: on_key_press(wm, &e.xkey); break; + case KeyRelease: on_key_release(wm, &e.xkey); break; case EnterNotify: on_enter_notify(wm, &e.xcrossing); break; case ClientMessage: on_client_message(wm, &e.xclient); break; case PropertyNotify: on_property_notify(wm, &e.xproperty); break;