920 lines
36 KiB
C++
920 lines
36 KiB
C++
/*
|
|
* input.cpp
|
|
* Mouse and keyboard event handling
|
|
* Copyright (c) 2026 Daniel Hammer
|
|
*/
|
|
|
|
#include "desktop_internal.hpp"
|
|
#include <montauk/user.h>
|
|
|
|
bool gui::desktop_refresh_keyboard_layouts(DesktopState* ds, bool force) {
|
|
if (!ds) return false;
|
|
uint64_t now = montauk::get_milliseconds();
|
|
if (!force && now - ds->keyboard_last_poll < 1000) return false;
|
|
|
|
// Only the selection can change while we run: the layout tables are
|
|
// read-only OS data, so there is no reason to re-parse them every second.
|
|
int prev_active = ds->keyboard.active;
|
|
bool prev_enabled[montauk::keyboard::MAX_LAYOUTS];
|
|
for (int i = 0; i < montauk::keyboard::MAX_LAYOUTS; i++)
|
|
prev_enabled[i] = ds->keyboard.enabled[i];
|
|
|
|
montauk::keyboard::refresh_selection(&ds->keyboard, ds->current_user);
|
|
|
|
bool changed = ds->keyboard.active != prev_active;
|
|
for (int i = 0; !changed && i < ds->keyboard.registry.count; i++)
|
|
changed = ds->keyboard.enabled[i] != prev_enabled[i];
|
|
ds->keyboard_last_poll = now;
|
|
return changed;
|
|
}
|
|
|
|
static bool try_unlock(DesktopState* ds) {
|
|
ds->lock_show_error = false;
|
|
|
|
if (montauk::user::authenticate(ds->current_user, ds->lock_password)) {
|
|
ds->screen_locked = false;
|
|
ds->lock_password[0] = '\0';
|
|
ds->lock_password_len = 0;
|
|
return true;
|
|
}
|
|
|
|
montauk::strcpy(ds->lock_error, "Incorrect password");
|
|
ds->lock_show_error = true;
|
|
ds->lock_password[0] = '\0';
|
|
ds->lock_password_len = 0;
|
|
return false;
|
|
}
|
|
|
|
static void handle_lock_mouse(DesktopState* ds) {
|
|
int mx = ds->mouse.x;
|
|
int my = ds->mouse.y;
|
|
uint8_t buttons = ds->mouse.buttons;
|
|
uint8_t prev = ds->prev_buttons;
|
|
bool left_pressed = (buttons & 0x01) && !(prev & 0x01);
|
|
|
|
if (!left_pressed) return;
|
|
|
|
int sfh = system_font_height();
|
|
int card_w = 360;
|
|
int content_w = card_w - 48;
|
|
int field_h = 36;
|
|
int btn_h = 40;
|
|
|
|
// Calculate card layout to find button position
|
|
int error_h = ds->lock_show_error ? sfh + 8 : 0;
|
|
int card_h = 20 + sfh + 16 + sfh + 12 + sfh + 4 + field_h + 16 + btn_h + error_h + 20;
|
|
int card_x = (ds->screen_w - card_w) / 2;
|
|
int card_y = (ds->screen_h - card_h) / 2;
|
|
int x = card_x + 24;
|
|
|
|
// Walk through the layout to find the Unlock button y position
|
|
int y = card_y + 20;
|
|
y += sfh + 16; // title
|
|
y += sfh + 12; // username
|
|
y += sfh + 4; // "Password" label
|
|
y += field_h + 16; // field + gap
|
|
|
|
// Check Unlock button
|
|
if (mx >= x && mx < x + content_w && my >= y && my < y + btn_h) {
|
|
try_unlock(ds);
|
|
return;
|
|
}
|
|
|
|
// Check password field click (just keep focus, nothing else to switch to)
|
|
int field_y = y - field_h - 16;
|
|
if (mx >= x && mx < x + content_w && my >= field_y && my < field_y + field_h) {
|
|
ds->lock_show_error = false;
|
|
}
|
|
}
|
|
|
|
static void handle_lock_keyboard(DesktopState* ds, const montauk::abi::KeyEvent& key) {
|
|
if (!key.pressed) return;
|
|
|
|
// Enter submits
|
|
if (key.ascii == '\n' || key.ascii == '\r') {
|
|
try_unlock(ds);
|
|
return;
|
|
}
|
|
|
|
// Backspace
|
|
if (key.ascii == '\b' || key.scancode == 0x0E) {
|
|
if (ds->lock_password_len > 0) {
|
|
ds->lock_password_len--;
|
|
ds->lock_password[ds->lock_password_len] = '\0';
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Printable characters
|
|
if ((unsigned char)key.ascii >= 0x20
|
|
&& (unsigned char)key.ascii != 0x7F) {
|
|
if (ds->lock_password_len < 63) {
|
|
ds->lock_password[ds->lock_password_len] = key.ascii;
|
|
ds->lock_password_len++;
|
|
ds->lock_password[ds->lock_password_len] = '\0';
|
|
}
|
|
}
|
|
}
|
|
|
|
static void forward_external_mouse(gui::Window* win,
|
|
const gui::MouseEvent& ev,
|
|
int mx,
|
|
int my,
|
|
uint8_t buttons,
|
|
uint8_t prev) {
|
|
gui::Rect cr = desktop_content_rect(win);
|
|
montauk::abi::WinEvent wev;
|
|
montauk::memset(&wev, 0, sizeof(wev));
|
|
wev.type = 1; // mouse
|
|
wev.mouse.x = mx - cr.x;
|
|
wev.mouse.y = my - cr.y;
|
|
wev.mouse.scroll = ev.scroll;
|
|
wev.mouse.buttons = buttons;
|
|
wev.mouse.prev_buttons = prev;
|
|
montauk::win_sendevent(win->ext_win_id, &wev);
|
|
}
|
|
|
|
static bool route_fullscreen_external_mouse(gui::DesktopState* ds,
|
|
const gui::MouseEvent& ev,
|
|
int mx,
|
|
int my,
|
|
uint8_t buttons,
|
|
uint8_t prev) {
|
|
for (int i = ds->window_count - 1; i >= 0; i--) {
|
|
gui::Window* win = &ds->windows[i];
|
|
if (win->state == gui::WIN_MINIMIZED || win->state == gui::WIN_CLOSED)
|
|
continue;
|
|
if (!desktop_window_fullscreen(win))
|
|
return false;
|
|
|
|
if (i != ds->window_count - 1) {
|
|
gui::desktop_raise_window(ds, i);
|
|
win = &ds->windows[ds->window_count - 1];
|
|
}
|
|
forward_external_mouse(win, ev, mx, my, buttons, prev);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Desktop Mouse Handling
|
|
// ============================================================================
|
|
|
|
void gui::desktop_handle_mouse(DesktopState* ds) {
|
|
if (ds->screen_locked) {
|
|
handle_lock_mouse(ds);
|
|
return;
|
|
}
|
|
|
|
int mx = ds->mouse.x;
|
|
int my = ds->mouse.y;
|
|
uint8_t buttons = ds->mouse.buttons;
|
|
uint8_t prev = ds->prev_buttons;
|
|
bool left_pressed = (buttons & 0x01) && !(prev & 0x01);
|
|
bool left_held = (buttons & 0x01);
|
|
bool left_released = !(buttons & 0x01) && (prev & 0x01);
|
|
bool right_pressed = (buttons & 0x02) && !(prev & 0x02);
|
|
|
|
MouseEvent ev;
|
|
ev.x = mx;
|
|
ev.y = my;
|
|
ev.buttons = buttons;
|
|
ev.prev_buttons = prev;
|
|
ev.scroll = ds->mouse.scrollDelta;
|
|
|
|
if (route_fullscreen_external_mouse(ds, ev, mx, my, buttons, prev)) {
|
|
ds->app_menu_open = false;
|
|
gui::mtk::context_menu_close(ds->ctx_menu);
|
|
ds->vol_popup_open = false;
|
|
ds->net_popup_open = false;
|
|
ds->wifi_popup_open = false;
|
|
return;
|
|
}
|
|
|
|
if (ds->launcher_open) {
|
|
desktop_handle_launcher_mouse(ds);
|
|
return;
|
|
}
|
|
|
|
// Handle context menu clicks
|
|
if (ds->ctx_menu.open) {
|
|
gui::mtk::ContextMenuItem ctx_items[DESKTOP_CTX_COUNT];
|
|
desktop_ctx_menu_items(ctx_items);
|
|
int action = gui::mtk::context_menu_handle_mouse(
|
|
ds->ctx_menu, ctx_items, DESKTOP_CTX_COUNT, mx, my, buttons, prev,
|
|
ds->screen_w, ds->screen_h, DESKTOP_CTX_MENU_W, DESKTOP_CTX_ITEM_H);
|
|
|
|
switch (action) {
|
|
case DESKTOP_CTX_TERMINAL: open_terminal(ds); return;
|
|
case DESKTOP_CTX_FILES: open_filemanager(ds); return;
|
|
case DESKTOP_CTX_SETTINGS:
|
|
desktop_launch_builtin(ds, DESKTOP_BUILTIN_SYSTEM_CONFIGURATION);
|
|
return;
|
|
case DESKTOP_CTX_SLEEP:
|
|
desktop_launch_builtin(ds, DESKTOP_BUILTIN_SLEEP);
|
|
return;
|
|
case DESKTOP_CTX_REBOOT:
|
|
desktop_launch_builtin(ds, DESKTOP_BUILTIN_REBOOT);
|
|
return;
|
|
case DESKTOP_CTX_SHUTDOWN:
|
|
desktop_launch_builtin(ds, DESKTOP_BUILTIN_SHUTDOWN);
|
|
return;
|
|
default: break;
|
|
}
|
|
// A click outside the menu just dismisses it; plain motion (CONSUMED)
|
|
// falls through so window hover and drags keep working.
|
|
if (action == gui::mtk::CONTEXT_MENU_DISMISSED) return;
|
|
}
|
|
|
|
// Check for ongoing window drags first
|
|
for (int i = 0; i < ds->window_count; i++) {
|
|
Window* win = &ds->windows[i];
|
|
if (win->dragging) {
|
|
if (left_held) {
|
|
win->frame.x = mx - win->drag_offset_x;
|
|
win->frame.y = my - win->drag_offset_y;
|
|
if (win->frame.x < -win->frame.w + 50) win->frame.x = -win->frame.w + 50;
|
|
if (win->frame.y < 0) win->frame.y = 0;
|
|
if (win->frame.x > ds->screen_w - 50) win->frame.x = ds->screen_w - 50;
|
|
if (win->frame.y > ds->screen_h - 50) win->frame.y = ds->screen_h - 50;
|
|
}
|
|
if (left_released) {
|
|
win->dragging = false;
|
|
// Window edge snapping
|
|
if (mx <= 0) {
|
|
win->saved_frame = win->frame;
|
|
win->frame = {0, PANEL_HEIGHT, ds->screen_w / 2, ds->screen_h - PANEL_HEIGHT};
|
|
win->state = WIN_MAXIMIZED;
|
|
if (!win->external) {
|
|
Rect cr = desktop_content_rect(win);
|
|
if (cr.w != win->content_w || cr.h != win->content_h) {
|
|
if (win->content) montauk::free(win->content);
|
|
win->content_w = cr.w;
|
|
win->content_h = cr.h;
|
|
win->content = (uint32_t*)montauk::alloc(cr.w * cr.h * 4);
|
|
montauk::memset(win->content, 0xFF, cr.w * cr.h * 4);
|
|
}
|
|
} else {
|
|
Rect cr = desktop_content_rect(win);
|
|
montauk::abi::WinEvent rev;
|
|
montauk::memset(&rev, 0, sizeof(rev));
|
|
rev.type = 2;
|
|
rev.resize.w = cr.w;
|
|
rev.resize.h = cr.h;
|
|
montauk::win_sendevent(win->ext_win_id, &rev);
|
|
}
|
|
} else if (mx >= ds->screen_w - 1) {
|
|
win->saved_frame = win->frame;
|
|
win->frame = {ds->screen_w / 2, PANEL_HEIGHT, ds->screen_w / 2, ds->screen_h - PANEL_HEIGHT};
|
|
win->state = WIN_MAXIMIZED;
|
|
if (!win->external) {
|
|
Rect cr = desktop_content_rect(win);
|
|
if (cr.w != win->content_w || cr.h != win->content_h) {
|
|
if (win->content) montauk::free(win->content);
|
|
win->content_w = cr.w;
|
|
win->content_h = cr.h;
|
|
win->content = (uint32_t*)montauk::alloc(cr.w * cr.h * 4);
|
|
montauk::memset(win->content, 0xFF, cr.w * cr.h * 4);
|
|
}
|
|
} else {
|
|
Rect cr = desktop_content_rect(win);
|
|
montauk::abi::WinEvent rev;
|
|
montauk::memset(&rev, 0, sizeof(rev));
|
|
rev.type = 2;
|
|
rev.resize.w = cr.w;
|
|
rev.resize.h = cr.h;
|
|
montauk::win_sendevent(win->ext_win_id, &rev);
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Check for ongoing window resizes
|
|
for (int i = 0; i < ds->window_count; i++) {
|
|
Window* win = &ds->windows[i];
|
|
if (win->resizing) {
|
|
if (left_held) {
|
|
int dx = mx - win->resize_start_mx;
|
|
int dy = my - win->resize_start_my;
|
|
Rect sf = win->resize_start_frame;
|
|
ResizeEdge edge = win->resize_edge;
|
|
|
|
int new_x = sf.x, new_y = sf.y, new_w = sf.w, new_h = sf.h;
|
|
|
|
if (edge == RESIZE_RIGHT || edge == RESIZE_TOP_RIGHT || edge == RESIZE_BOTTOM_RIGHT)
|
|
new_w = sf.w + dx;
|
|
if (edge == RESIZE_BOTTOM || edge == RESIZE_BOTTOM_LEFT || edge == RESIZE_BOTTOM_RIGHT)
|
|
new_h = sf.h + dy;
|
|
if (edge == RESIZE_LEFT || edge == RESIZE_TOP_LEFT || edge == RESIZE_BOTTOM_LEFT) {
|
|
new_x = sf.x + dx;
|
|
new_w = sf.w - dx;
|
|
}
|
|
if (edge == RESIZE_TOP || edge == RESIZE_TOP_LEFT || edge == RESIZE_TOP_RIGHT) {
|
|
new_y = sf.y + dy;
|
|
new_h = sf.h - dy;
|
|
}
|
|
|
|
// Enforce minimum size
|
|
if (new_w < MIN_WINDOW_W) {
|
|
if (edge == RESIZE_LEFT || edge == RESIZE_TOP_LEFT || edge == RESIZE_BOTTOM_LEFT)
|
|
new_x = sf.x + sf.w - MIN_WINDOW_W;
|
|
new_w = MIN_WINDOW_W;
|
|
}
|
|
if (new_h < MIN_WINDOW_H) {
|
|
if (edge == RESIZE_TOP || edge == RESIZE_TOP_LEFT || edge == RESIZE_TOP_RIGHT)
|
|
new_y = sf.y + sf.h - MIN_WINDOW_H;
|
|
new_h = MIN_WINDOW_H;
|
|
}
|
|
|
|
win->frame = {new_x, new_y, new_w, new_h};
|
|
}
|
|
if (left_released) {
|
|
win->resizing = false;
|
|
// Reallocate content buffer if dimensions changed (skip for external)
|
|
if (!win->external) {
|
|
Rect cr = desktop_content_rect(win);
|
|
if (cr.w != win->content_w || cr.h != win->content_h) {
|
|
if (win->content) montauk::free(win->content);
|
|
win->content_w = cr.w;
|
|
win->content_h = cr.h;
|
|
win->content = (uint32_t*)montauk::alloc(cr.w * cr.h * 4);
|
|
montauk::memset(win->content, 0xFF, cr.w * cr.h * 4);
|
|
}
|
|
win->dirty = true;
|
|
} else {
|
|
Rect cr = desktop_content_rect(win);
|
|
montauk::abi::WinEvent rev;
|
|
montauk::memset(&rev, 0, sizeof(rev));
|
|
rev.type = 2;
|
|
rev.resize.w = cr.w;
|
|
rev.resize.h = cr.h;
|
|
montauk::win_sendevent(win->ext_win_id, &rev);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Handle app menu clicks
|
|
if (ds->app_menu_open && left_pressed) {
|
|
int menu_x = 4;
|
|
int menu_y = PANEL_HEIGHT + 2;
|
|
int menu_h = menu_total_height();
|
|
Rect menu_rect = {menu_x, menu_y, MENU_W, menu_h};
|
|
|
|
if (menu_rect.contains(mx, my)) {
|
|
// Walk visible rows to find which one was clicked
|
|
int iy = menu_y + 5;
|
|
int cur_cat = -1;
|
|
for (int i = 0; i < menu_row_count; i++) {
|
|
const MenuRow& row = menu_rows[i];
|
|
if (row.is_category) cur_cat++;
|
|
if (!menu_row_visible(i)) continue;
|
|
int row_h = menu_row_height(row);
|
|
if (my >= iy && my < iy + row_h) {
|
|
if (row.is_category && row.label[0] && cur_cat >= 0 && cur_cat < MENU_NUM_CATS - 1) {
|
|
// Toggle category expand/collapse
|
|
menu_cat_expanded[cur_cat] = !menu_cat_expanded[cur_cat];
|
|
} else if (!row.is_category) {
|
|
if (row.external) {
|
|
if (row.launch_with_home) {
|
|
desktop_spawn_app(ds, row.binary_path, ds->home_dir);
|
|
} else {
|
|
desktop_spawn_app(ds, row.binary_path);
|
|
}
|
|
} else {
|
|
desktop_launch_builtin(ds, row.app_id);
|
|
}
|
|
ds->app_menu_open = false;
|
|
}
|
|
break;
|
|
}
|
|
iy += row_h;
|
|
}
|
|
return;
|
|
} else {
|
|
ds->app_menu_open = false;
|
|
}
|
|
}
|
|
|
|
// Handle volume popup interaction
|
|
if (ds->vol_popup_open) {
|
|
int popup_x = ds->vol_icon_rect.x + ds->vol_icon_rect.w - 200;
|
|
int popup_y = PANEL_HEIGHT + 2;
|
|
if (popup_x < 4) popup_x = 4;
|
|
Rect vol_rect = {popup_x, popup_y, 200, 202};
|
|
|
|
// Handle drag continuity
|
|
if (ds->vol_dragging) {
|
|
if (left_held) {
|
|
int slider_abs_x = popup_x + 16;
|
|
int v = ((mx - slider_abs_x) * 100) / (200 - 32);
|
|
if (v < 0) v = 0;
|
|
if (v > 100) v = 100;
|
|
if (ds->vol_muted) {
|
|
ds->vol_muted = false;
|
|
montauk::audio_set_master_mute(false);
|
|
}
|
|
ds->vol_level = v;
|
|
montauk::audio_set_master_volume(v);
|
|
return;
|
|
}
|
|
if (left_released) {
|
|
ds->vol_dragging = false;
|
|
}
|
|
}
|
|
|
|
if (left_pressed) {
|
|
if (vol_rect.contains(mx, my)) {
|
|
// Slider area (y = popup_y + 56, height = 8, with generous hit zone)
|
|
int slider_abs_x = popup_x + 16;
|
|
int slider_abs_y = popup_y + 56;
|
|
int slider_w = 200 - 32;
|
|
if (my >= slider_abs_y - 10 && my <= slider_abs_y + 8 + 10 &&
|
|
mx >= slider_abs_x - 8 && mx <= slider_abs_x + slider_w + 8) {
|
|
int v = ((mx - slider_abs_x) * 100) / slider_w;
|
|
if (v < 0) v = 0;
|
|
if (v > 100) v = 100;
|
|
if (ds->vol_muted) {
|
|
ds->vol_muted = false;
|
|
montauk::audio_set_master_mute(false);
|
|
}
|
|
ds->vol_level = v;
|
|
montauk::audio_set_master_volume(v);
|
|
ds->vol_dragging = true;
|
|
return;
|
|
}
|
|
|
|
// Buttons (y = popup_y + 78, h = 24)
|
|
int btn_h = 24;
|
|
int btn_y = popup_y + 78;
|
|
int minus_w = 36, plus_w = 36, mute_w = 50, gap = 8;
|
|
int total_w = minus_w + plus_w + mute_w + gap * 2;
|
|
int bx = popup_x + (200 - total_w) / 2;
|
|
|
|
if (my >= btn_y && my < btn_y + btn_h) {
|
|
// [-]
|
|
if (mx >= bx && mx < bx + minus_w) {
|
|
if (ds->vol_muted) {
|
|
ds->vol_muted = false;
|
|
montauk::audio_set_master_mute(false);
|
|
}
|
|
int v = ds->vol_level - 5;
|
|
if (v < 0) v = 0;
|
|
ds->vol_level = v;
|
|
montauk::audio_set_master_volume(v);
|
|
return;
|
|
}
|
|
bx += minus_w + gap;
|
|
// [+]
|
|
if (mx >= bx && mx < bx + plus_w) {
|
|
if (ds->vol_muted) {
|
|
ds->vol_muted = false;
|
|
montauk::audio_set_master_mute(false);
|
|
}
|
|
int v = ds->vol_level + 5;
|
|
if (v > 100) v = 100;
|
|
ds->vol_level = v;
|
|
montauk::audio_set_master_volume(v);
|
|
return;
|
|
}
|
|
bx += plus_w + gap;
|
|
// [Mute]
|
|
if (mx >= bx && mx < bx + mute_w) {
|
|
if (ds->vol_muted) {
|
|
ds->vol_muted = false;
|
|
montauk::audio_set_master_mute(false);
|
|
montauk::audio_set_master_volume(ds->vol_pre_mute);
|
|
ds->vol_level = ds->vol_pre_mute;
|
|
} else {
|
|
ds->vol_pre_mute = ds->vol_level;
|
|
ds->vol_muted = true;
|
|
montauk::audio_set_master_mute(true);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Output device radio rows.
|
|
Rect hda_r = {popup_x + 12, popup_y + 137, 200 - 24, 25};
|
|
Rect bt_r = {hda_r.x, hda_r.y + hda_r.h, hda_r.w, hda_r.h};
|
|
if (hda_r.contains(mx, my) &&
|
|
ds->vol_output != montauk::abi::AUDIO_OUTPUT_HDA) {
|
|
if (montauk::audio_set_output(montauk::abi::AUDIO_OUTPUT_HDA) == 0)
|
|
ds->vol_output = montauk::abi::AUDIO_OUTPUT_HDA;
|
|
return;
|
|
}
|
|
if (bt_r.contains(mx, my) && ds->vol_bt_status >= 2 &&
|
|
ds->vol_output != montauk::abi::AUDIO_OUTPUT_BLUETOOTH) {
|
|
if (montauk::audio_set_output(
|
|
montauk::abi::AUDIO_OUTPUT_BLUETOOTH) == 0)
|
|
ds->vol_output = montauk::abi::AUDIO_OUTPUT_BLUETOOTH;
|
|
return;
|
|
}
|
|
return; // click inside popup but not on any control
|
|
} else if (!ds->vol_icon_rect.contains(mx, my)) {
|
|
ds->vol_popup_open = false;
|
|
ds->vol_dragging = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Wi-Fi popup. The passphrase dialog is an ordinary window and needs
|
|
// nothing here.
|
|
if (ds->wifi_popup_open) {
|
|
if (desktop_wifi_handle_mouse(ds, mx, my, left_pressed, ev.scroll))
|
|
return;
|
|
}
|
|
|
|
// Handle net popup clicks
|
|
if (ds->net_popup_open && left_pressed) {
|
|
int popup_w = 220;
|
|
int fh_net = system_font_height();
|
|
int row_h_net = fh_net + 8;
|
|
int popup_h = (row_h_net + 8) + row_h_net * 5 + 12;
|
|
int popup_x = ds->net_icon_rect.x + ds->net_icon_rect.w - popup_w;
|
|
int popup_y = PANEL_HEIGHT + 2;
|
|
if (popup_x < 4) popup_x = 4;
|
|
Rect popup_rect = {popup_x, popup_y, popup_w, popup_h};
|
|
|
|
if (popup_rect.contains(mx, my)) {
|
|
return;
|
|
} else if (!ds->net_icon_rect.contains(mx, my)) {
|
|
ds->net_popup_open = false;
|
|
ds->wifi_popup_open = false;
|
|
}
|
|
}
|
|
|
|
// Panel click check
|
|
if (left_pressed && my < PANEL_HEIGHT) {
|
|
// App menu button
|
|
if (mx < 36) {
|
|
ds->app_menu_open = !ds->app_menu_open;
|
|
ds->net_popup_open = false;
|
|
ds->wifi_popup_open = false;
|
|
ds->vol_popup_open = false;
|
|
gui::mtk::context_menu_close(ds->ctx_menu);
|
|
return;
|
|
}
|
|
|
|
// Keyboard layout indicator: cycle through the layouts enabled in the
|
|
// Keyboard Settings applet and persist the new active choice.
|
|
if (ds->keyboard_layout_rect.w > 0
|
|
&& ds->keyboard_layout_rect.contains(mx, my)) {
|
|
ds->keyboard.active = montauk::keyboard::next_enabled(
|
|
ds->keyboard, ds->keyboard.active);
|
|
montauk::keyboard::save_user(ds->current_user, ds->keyboard);
|
|
ds->keyboard_last_poll = montauk::get_milliseconds();
|
|
ds->app_menu_open = false;
|
|
ds->net_popup_open = false;
|
|
ds->wifi_popup_open = false;
|
|
ds->vol_popup_open = false;
|
|
gui::mtk::context_menu_close(ds->ctx_menu);
|
|
return;
|
|
}
|
|
|
|
// Volume icon
|
|
if (ds->vol_icon_rect.w > 0 && ds->vol_icon_rect.contains(mx, my)) {
|
|
ds->vol_popup_open = !ds->vol_popup_open;
|
|
ds->vol_dragging = false;
|
|
ds->app_menu_open = false;
|
|
ds->net_popup_open = false;
|
|
ds->wifi_popup_open = false;
|
|
gui::mtk::context_menu_close(ds->ctx_menu);
|
|
return;
|
|
}
|
|
|
|
// Network icon
|
|
if (ds->net_icon_rect.w > 0 && ds->net_icon_rect.contains(mx, my)) {
|
|
ds->net_popup_open = !ds->net_popup_open;
|
|
ds->app_menu_open = false;
|
|
ds->vol_popup_open = false;
|
|
ds->wifi_popup_open = false;
|
|
gui::mtk::context_menu_close(ds->ctx_menu);
|
|
return;
|
|
}
|
|
|
|
// Wi-Fi icon
|
|
if (ds->wifi_icon_rect.w > 0 && ds->wifi_icon_rect.contains(mx, my)) {
|
|
ds->wifi_popup_open = !ds->wifi_popup_open;
|
|
ds->app_menu_open = false;
|
|
ds->vol_popup_open = false;
|
|
ds->net_popup_open = false;
|
|
gui::mtk::context_menu_close(ds->ctx_menu);
|
|
return;
|
|
}
|
|
// Window indicator buttons
|
|
int indicator_x = 40;
|
|
for (int i = 0; i < ds->window_count; i++) {
|
|
Window* win = &ds->windows[i];
|
|
if (win->state == WIN_CLOSED) continue;
|
|
|
|
int tw = text_width(win->title);
|
|
int pad = 12;
|
|
int iw = tw + pad * 2;
|
|
if (iw > 150) iw = 150;
|
|
|
|
Rect btn_rect = {indicator_x, 4, iw, 24};
|
|
if (btn_rect.contains(mx, my)) {
|
|
if (win->state == WIN_MINIMIZED) {
|
|
win->state = WIN_NORMAL;
|
|
}
|
|
desktop_raise_window(ds, i);
|
|
return;
|
|
}
|
|
indicator_x += iw + 4;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Window interaction: check from top (last) to bottom (first)
|
|
if (left_pressed) {
|
|
for (int i = ds->window_count - 1; i >= 0; i--) {
|
|
Window* win = &ds->windows[i];
|
|
if (win->state == WIN_MINIMIZED || win->state == WIN_CLOSED) continue;
|
|
|
|
if (!desktop_window_fullscreen(win)) {
|
|
// Check close button
|
|
Rect close_r = win->close_btn_rect();
|
|
if (close_r.contains(mx, my)) {
|
|
desktop_close_window(ds, i);
|
|
return;
|
|
}
|
|
|
|
// Check minimize button
|
|
Rect min_r = win->min_btn_rect();
|
|
if (min_r.contains(mx, my)) {
|
|
win->state = WIN_MINIMIZED;
|
|
if (ds->focused_window == i) {
|
|
ds->focused_window = -1;
|
|
for (int j = ds->window_count - 1; j >= 0; j--) {
|
|
if (ds->windows[j].state == WIN_NORMAL || ds->windows[j].state == WIN_MAXIMIZED) {
|
|
ds->focused_window = j;
|
|
ds->windows[j].focused = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Check maximize button
|
|
Rect max_r = win->max_btn_rect();
|
|
if (max_r.contains(mx, my)) {
|
|
if (win->state == WIN_MAXIMIZED) {
|
|
win->frame = win->saved_frame;
|
|
win->state = WIN_NORMAL;
|
|
} else {
|
|
win->saved_frame = win->frame;
|
|
win->frame = {0, PANEL_HEIGHT, ds->screen_w, ds->screen_h - PANEL_HEIGHT};
|
|
win->state = WIN_MAXIMIZED;
|
|
}
|
|
// Reallocate content buffer for local windows only
|
|
if (!win->external) {
|
|
Rect cr = desktop_content_rect(win);
|
|
if (cr.w != win->content_w || cr.h != win->content_h) {
|
|
if (win->content) montauk::free(win->content);
|
|
win->content_w = cr.w;
|
|
win->content_h = cr.h;
|
|
win->content = (uint32_t*)montauk::alloc(cr.w * cr.h * 4);
|
|
montauk::memset(win->content, 0xFF, cr.w * cr.h * 4);
|
|
}
|
|
} else {
|
|
Rect cr = desktop_content_rect(win);
|
|
montauk::abi::WinEvent rev;
|
|
montauk::memset(&rev, 0, sizeof(rev));
|
|
rev.type = 2;
|
|
rev.resize.w = cr.w;
|
|
rev.resize.h = cr.h;
|
|
montauk::win_sendevent(win->ext_win_id, &rev);
|
|
}
|
|
desktop_raise_window(ds, i);
|
|
return;
|
|
}
|
|
|
|
// Check resize edges (before titlebar drag, so corner grabs work)
|
|
if (win->state != WIN_MAXIMIZED) {
|
|
ResizeEdge edge = hit_test_resize_edge(win->frame, mx, my);
|
|
if (edge != RESIZE_NONE) {
|
|
desktop_raise_window(ds, i);
|
|
int new_idx = ds->window_count - 1;
|
|
ds->windows[new_idx].resizing = true;
|
|
ds->windows[new_idx].resize_edge = edge;
|
|
ds->windows[new_idx].resize_start_frame = ds->windows[new_idx].frame;
|
|
ds->windows[new_idx].resize_start_mx = mx;
|
|
ds->windows[new_idx].resize_start_my = my;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Check titlebar (start drag)
|
|
Rect tb = win->titlebar_rect();
|
|
if (tb.contains(mx, my)) {
|
|
desktop_raise_window(ds, i);
|
|
int new_idx = ds->window_count - 1;
|
|
ds->windows[new_idx].dragging = true;
|
|
ds->windows[new_idx].drag_offset_x = mx - ds->windows[new_idx].frame.x;
|
|
ds->windows[new_idx].drag_offset_y = my - ds->windows[new_idx].frame.y;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Check content area
|
|
Rect cr = desktop_content_rect(win);
|
|
if (cr.contains(mx, my)) {
|
|
desktop_raise_window(ds, i);
|
|
int new_idx = ds->window_count - 1;
|
|
Window* raised = &ds->windows[new_idx];
|
|
if (raised->external) {
|
|
// Forward mouse event to external window
|
|
montauk::abi::WinEvent wev;
|
|
montauk::memset(&wev, 0, sizeof(wev));
|
|
wev.type = 1; // mouse
|
|
wev.mouse.x = mx - cr.x;
|
|
wev.mouse.y = my - cr.y;
|
|
wev.mouse.scroll = ev.scroll;
|
|
wev.mouse.buttons = buttons;
|
|
wev.mouse.prev_buttons = prev;
|
|
montauk::win_sendevent(raised->ext_win_id, &wev);
|
|
} else if (raised->on_mouse) {
|
|
ev.x = mx;
|
|
ev.y = my;
|
|
raised->on_mouse(raised, ev);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Check full frame
|
|
if (win->frame.contains(mx, my)) {
|
|
desktop_raise_window(ds, i);
|
|
return;
|
|
}
|
|
}
|
|
|
|
ds->app_menu_open = false;
|
|
gui::mtk::context_menu_close(ds->ctx_menu);
|
|
ds->vol_popup_open = false;
|
|
}
|
|
|
|
// Forward continuous mouse events to focused window (hover, drag, release).
|
|
// Scroll is sent separately below; clearing ev.scroll here prevents local
|
|
// on_mouse handlers from receiving the same scroll twice.
|
|
if (!left_pressed && ds->focused_window >= 0 && ds->focused_window < ds->window_count) {
|
|
Window* win = &ds->windows[ds->focused_window];
|
|
if (win->state != WIN_MINIMIZED && win->state != WIN_CLOSED) {
|
|
Rect cr = desktop_content_rect(win);
|
|
// Forward if mouse is over content (hover/move) or button is held/released (drag continuity)
|
|
if (cr.contains(mx, my) || left_held || left_released) {
|
|
if (win->external) {
|
|
montauk::abi::WinEvent wev;
|
|
montauk::memset(&wev, 0, sizeof(wev));
|
|
wev.type = 1; // mouse
|
|
wev.mouse.x = mx - cr.x;
|
|
wev.mouse.y = my - cr.y;
|
|
wev.mouse.scroll = 0;
|
|
wev.mouse.buttons = buttons;
|
|
wev.mouse.prev_buttons = prev;
|
|
montauk::win_sendevent(win->ext_win_id, &wev);
|
|
} else if (win->on_mouse) {
|
|
MouseEvent hover = ev;
|
|
hover.x = mx;
|
|
hover.y = my;
|
|
hover.scroll = 0;
|
|
win->on_mouse(win, hover);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle scroll events on focused window
|
|
if (ev.scroll != 0 && ds->focused_window >= 0 && ds->focused_window < ds->window_count) {
|
|
Window* win = &ds->windows[ds->focused_window];
|
|
Rect cr = desktop_content_rect(win);
|
|
if (cr.contains(mx, my)) {
|
|
if (win->external) {
|
|
montauk::abi::WinEvent wev;
|
|
montauk::memset(&wev, 0, sizeof(wev));
|
|
wev.type = 1; // mouse
|
|
wev.mouse.x = mx - cr.x;
|
|
wev.mouse.y = my - cr.y;
|
|
wev.mouse.scroll = ev.scroll;
|
|
wev.mouse.buttons = buttons;
|
|
wev.mouse.prev_buttons = prev;
|
|
montauk::win_sendevent(win->ext_win_id, &wev);
|
|
} else if (win->on_mouse) {
|
|
win->on_mouse(win, ev);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Right-click on desktop background opens context menu
|
|
if (right_pressed && my >= PANEL_HEIGHT) {
|
|
bool on_window = false;
|
|
for (int i = ds->window_count - 1; i >= 0; i--) {
|
|
Window* win = &ds->windows[i];
|
|
if (win->state == WIN_MINIMIZED || win->state == WIN_CLOSED) continue;
|
|
if (win->frame.contains(mx, my)) {
|
|
on_window = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!on_window) {
|
|
gui::mtk::context_menu_open(ds->ctx_menu, mx, my);
|
|
// Pin the menu inside the screen up front so hit-testing and
|
|
// drawing agree on where it landed.
|
|
Rect r = gui::mtk::context_menu_rect(ds->ctx_menu, DESKTOP_CTX_COUNT,
|
|
ds->screen_w, ds->screen_h,
|
|
DESKTOP_CTX_MENU_W, DESKTOP_CTX_ITEM_H);
|
|
ds->ctx_menu.x = r.x;
|
|
ds->ctx_menu.y = r.y;
|
|
ds->app_menu_open = false;
|
|
ds->net_popup_open = false;
|
|
ds->wifi_popup_open = false;
|
|
ds->vol_popup_open = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
void gui::desktop_handle_keyboard(DesktopState* ds, const montauk::abi::KeyEvent& key) {
|
|
montauk::abi::KeyEvent translated_key = key;
|
|
montauk::keyboard::translate(ds->keyboard, &translated_key);
|
|
const montauk::abi::KeyEvent& routed_key = translated_key;
|
|
|
|
if (ds->screen_locked) {
|
|
handle_lock_keyboard(ds, routed_key);
|
|
return;
|
|
}
|
|
|
|
for (int i = ds->window_count - 1; i >= 0; i--) {
|
|
Window* win = &ds->windows[i];
|
|
if (win->state == WIN_MINIMIZED || win->state == WIN_CLOSED)
|
|
continue;
|
|
if (!desktop_window_fullscreen(win))
|
|
break;
|
|
|
|
montauk::abi::WinEvent ev;
|
|
montauk::memset(&ev, 0, sizeof(ev));
|
|
ev.type = 0; // key
|
|
ev.key = routed_key;
|
|
montauk::win_sendevent(win->ext_win_id, &ev);
|
|
ds->launcher_open = false;
|
|
ds->app_menu_open = false;
|
|
gui::mtk::context_menu_close(ds->ctx_menu);
|
|
return;
|
|
}
|
|
|
|
if (desktop_handle_launcher_keyboard(ds, routed_key)) {
|
|
return;
|
|
}
|
|
|
|
// Global shortcuts (only on key press)
|
|
if (routed_key.pressed && routed_key.ctrl && routed_key.alt) {
|
|
if (routed_key.ascii == 'l' || routed_key.ascii == 'L') {
|
|
desktop_lock_screen(ds);
|
|
return;
|
|
}
|
|
if (routed_key.ascii == 't' || routed_key.ascii == 'T') {
|
|
open_terminal(ds);
|
|
return;
|
|
}
|
|
if (routed_key.ascii == 'f' || routed_key.ascii == 'F') {
|
|
open_filemanager(ds);
|
|
return;
|
|
}
|
|
if (routed_key.ascii == 'c' || routed_key.ascii == 'C') {
|
|
open_calculator(ds);
|
|
return;
|
|
}
|
|
if (routed_key.ascii == 'e' || routed_key.ascii == 'E') {
|
|
open_texteditor(ds);
|
|
return;
|
|
}
|
|
if (routed_key.ascii == 'w' || routed_key.ascii == 'W') {
|
|
open_wordprocessor(ds);
|
|
return;
|
|
}
|
|
if (routed_key.ascii == 'k' || routed_key.ascii == 'K') {
|
|
open_syslog(ds);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Dispatch to focused window
|
|
if (ds->focused_window >= 0 && ds->focused_window < ds->window_count) {
|
|
Window* win = &ds->windows[ds->focused_window];
|
|
if (win->external) {
|
|
// Forward key event to external window via syscall
|
|
montauk::abi::WinEvent ev;
|
|
montauk::memset(&ev, 0, sizeof(ev));
|
|
ev.type = 0; // key
|
|
ev.key = routed_key;
|
|
montauk::win_sendevent(win->ext_win_id, &ev);
|
|
} else if (win->on_key) {
|
|
win->on_key(win, routed_key);
|
|
}
|
|
}
|
|
}
|