Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/manager/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ add_executable(tenbox-manager WIN32
${CMAKE_SOURCE_DIR}/src/manager/ui/llm_proxy_dialog.cpp
${CMAKE_SOURCE_DIR}/src/manager/ui/settings_dialog.cpp
${CMAKE_SOURCE_DIR}/src/manager/ui/win32_display_panel.cpp
${CMAKE_SOURCE_DIR}/src/manager/ui/win32_fullscreen_window.cpp
${CMAKE_SOURCE_DIR}/src/manager/ui/win32_floating_toolbar.cpp
${CMAKE_SOURCE_DIR}/src/manager/ui/win32_fullscreen_osd.cpp
${CMAKE_SOURCE_DIR}/src/manager/ui/info_tab.cpp
${CMAKE_SOURCE_DIR}/src/manager/ui/console_tab.cpp
${CMAKE_SOURCE_DIR}/src/manager/ui/vm_listview.cpp
Expand Down
17 changes: 17 additions & 0 deletions src/manager/app_settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,15 @@ AppSettings LoadSettings(const std::string& data_dir) {
if (w.contains("width")) s.window.width = w["width"].get<int>();
if (w.contains("height")) s.window.height = w["height"].get<int>();
}
if (j.contains("fullscreen_toolbar") && j["fullscreen_toolbar"].is_object()) {
auto& ft = j["fullscreen_toolbar"];
if (ft.contains("snap_edge")) s.fullscreen_toolbar.snap_edge = ft["snap_edge"].get<int>();
if (ft.contains("offset")) s.fullscreen_toolbar.offset = ft["offset"].get<int>();
if (ft.contains("pinned")) s.fullscreen_toolbar.pinned = ft["pinned"].get<bool>();
}
if (j.contains("fullscreen_monitor_index") && j["fullscreen_monitor_index"].is_number()) {
s.fullscreen_monitor_index = j["fullscreen_monitor_index"].get<int>();
}
if (j.contains("show_toolbar") && j["show_toolbar"].is_boolean()) {
s.show_toolbar = j["show_toolbar"].get<bool>();
}
Expand Down Expand Up @@ -199,6 +208,14 @@ void SaveSettings(const std::string& data_dir, const AppSettings& s) {
j["window"] = w;
j["show_toolbar"] = s.show_toolbar;
j["close_to_tray"] = s.close_to_tray;
{
json ft;
ft["snap_edge"] = s.fullscreen_toolbar.snap_edge;
ft["offset"] = s.fullscreen_toolbar.offset;
ft["pinned"] = s.fullscreen_toolbar.pinned;
j["fullscreen_toolbar"] = ft;
}
j["fullscreen_monitor_index"] = s.fullscreen_monitor_index;
j["vm_paths"] = vm_paths_json;
if (!s.vm_storage_dir.empty())
j["vm_storage_dir"] = s.vm_storage_dir;
Expand Down
8 changes: 8 additions & 0 deletions src/manager/app_settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ struct WindowGeometry {
int width = 1024, height = 680;
};

struct FullscreenToolbarState {
int snap_edge = 0; // 0=top, 1=bottom, 2=left, 3=right
int offset = -1; // pixel offset along edge, -1 = centered
bool pinned = false;
};

enum class LlmApiType : uint8_t {
kOpenAiCompletions = 0, // POST /v1/chat/completions
// Future:
Expand All @@ -41,6 +47,8 @@ struct LlmProxySettings {

struct AppSettings {
WindowGeometry window;
FullscreenToolbarState fullscreen_toolbar;
int fullscreen_monitor_index = 0; // last monitor used for fullscreen
std::vector<std::string> vm_paths;
bool show_toolbar = true;
bool close_to_tray = true; // X button hides to system tray instead of quitting
Expand Down
18 changes: 18 additions & 0 deletions src/manager/i18n.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,15 @@ static const std::unordered_map<S, const char*> kStringsEn = {
{S::kCpuMemoryChangeWarning, "CPU / Memory changes require VM to be stopped"},
{S::kDisplayHintCaptured, "Full input capture (system keys) | Press Right Alt to release"},
{S::kDisplayHintNormal, "Click to capture system keys"},
{S::kMenuFullscreen, "&Fullscreen"},
{S::kToolbarFullscreen, "Fullscreen"},
{S::kFullscreenExit, "Exit Fullscreen"},
{S::kFullscreenPin, "Pin"},
{S::kFullscreenUnpin, "Unpin"},
{S::kFullscreenDpiZoom, "Zoom"},
{S::kFullscreenSwitchVm, "Switch VM"},
{S::kFullscreenMoveToMonitor, "Move to Monitor"},
{S::kFullscreenFindToolbar, "Find toolbar at top center of screen"},
{S::kMenuView, "View"},
{S::kMenuViewToolbar, "Toolbar"},
{S::kMenuHelp, "Help"},
Expand Down Expand Up @@ -355,6 +364,15 @@ static const std::unordered_map<S, const char*> kStringsZhCN = {
{S::kCpuMemoryChangeWarning, "更改 CPU/内存需要先停止虚拟机"},
{S::kDisplayHintCaptured, "已捕获全部输入(含系统键)| 按右 Alt 释放"},
{S::kDisplayHintNormal, "点击以捕获系统键"},
{S::kMenuFullscreen, "全屏(&F)"},
{S::kToolbarFullscreen, "全屏"},
{S::kFullscreenExit, "退出全屏"},
{S::kFullscreenPin, "固定"},
{S::kFullscreenUnpin, "取消固定"},
{S::kFullscreenDpiZoom, "缩放"},
{S::kFullscreenSwitchVm, "切换虚拟机"},
{S::kFullscreenMoveToMonitor, "移动到显示器"},
{S::kFullscreenFindToolbar, "可在屏幕顶部中间找回工具栏"},
{S::kMenuView, "视图"},
{S::kMenuViewToolbar, "工具栏"},
{S::kMenuHelp, "帮助"},
Expand Down
11 changes: 11 additions & 0 deletions src/manager/i18n.h
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,17 @@ enum class S {
kTrayHide,
kSettingsCloseToTray,

// Fullscreen
kMenuFullscreen,
kToolbarFullscreen,
kFullscreenExit,
kFullscreenPin,
kFullscreenUnpin,
kFullscreenDpiZoom,
kFullscreenSwitchVm,
kFullscreenMoveToMonitor,
kFullscreenFindToolbar,

kCount // Must be last
};

Expand Down
7 changes: 7 additions & 0 deletions src/manager/resource.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,10 @@
#define IDB_TOOLBAR_PORT_FORWARDS 109
#define IDB_TOOLBAR_DPI_ZOOM 110
#define IDB_TOOLBAR_LLM_PROXY 111
#define IDB_TOOLBAR_FULLSCREEN 112
#define IDB_FS_EXIT 113
#define IDB_FS_PIN 114
#define IDB_FS_UNPIN 115
#define IDB_FS_ZOOM 116
#define IDB_FS_ZOOM_ACTIVE 117
#define IDB_TOOLBAR_FULLSCREEN_EXIT 118
Binary file added src/manager/resources/fs_exit.bmp
Binary file not shown.
Binary file added src/manager/resources/fs_pin.bmp
Binary file not shown.
Binary file added src/manager/resources/fs_unpin.bmp
Binary file not shown.
Binary file added src/manager/resources/fs_zoom.bmp
Binary file not shown.
Binary file added src/manager/resources/fs_zoom_active.bmp
Binary file not shown.
Binary file added src/manager/resources/fullscreen.bmp
Binary file not shown.
Binary file added src/manager/resources/fullscreen_exit.bmp
Binary file not shown.
7 changes: 7 additions & 0 deletions src/manager/toolbar.rc
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ IDB_TOOLBAR_SHARED_FOLDERS BITMAP "resources/shared_folders.bmp"
IDB_TOOLBAR_PORT_FORWARDS BITMAP "resources/port_forwards.bmp"
IDB_TOOLBAR_DPI_ZOOM BITMAP "resources/dpi_zoom.bmp"
IDB_TOOLBAR_LLM_PROXY BITMAP "resources/llm_proxy.bmp"
IDB_TOOLBAR_FULLSCREEN BITMAP "resources/fullscreen.bmp"
IDB_FS_EXIT BITMAP "resources/fs_exit.bmp"
IDB_FS_PIN BITMAP "resources/fs_pin.bmp"
IDB_FS_UNPIN BITMAP "resources/fs_unpin.bmp"
IDB_FS_ZOOM BITMAP "resources/fs_zoom.bmp"
IDB_FS_ZOOM_ACTIVE BITMAP "resources/fs_zoom_active.bmp"
IDB_TOOLBAR_FULLSCREEN_EXIT BITMAP "resources/fullscreen_exit.bmp"

// Version information
VS_VERSION_INFO VERSIONINFO
Expand Down
36 changes: 33 additions & 3 deletions src/manager/ui/win32_display_panel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#include <algorithm>
#include <cstring>

extern HWND g_main_hwnd;

static const wchar_t* kDisplayPanelClass = L"TenBoxDisplayPanel";
static bool g_class_registered = false;

Expand Down Expand Up @@ -237,6 +239,14 @@ void DisplayPanel::SetVisible(bool visible) {
if (hwnd_) ShowWindow(hwnd_, visible ? SW_SHOW : SW_HIDE);
}

void DisplayPanel::Reparent(HWND new_parent) {
if (!hwnd_) return;
SetParent(hwnd_, new_parent);
RECT rc;
GetClientRect(new_parent, &rc);
SetBounds(0, 0, rc.right, rc.bottom);
}

void DisplayPanel::CalcDisplayRect(int cw, int ch, RECT* out) const {
if (fb_width_ == 0 || fb_height_ == 0 || cw <= 0 || ch <= 0) {
*out = {0, 0, cw, ch};
Expand Down Expand Up @@ -334,7 +344,7 @@ void DisplayPanel::OnPaint() {
int pill_w = text_sz.cx + pad_x * 2;
int pill_h = text_sz.cy + pad_y * 2;
int pill_x = (rc.right - pill_w) / 2;
int pill_y = 6;
int pill_y = 6 + hint_offset_y_;

RECT pill_rc = {pill_x, pill_y, pill_x + pill_w, pill_y + pill_h};
HBRUSH bg_brush = CreateSolidBrush(RGB(48, 48, 48));
Expand Down Expand Up @@ -484,6 +494,7 @@ void DisplayPanel::SetCaptured(bool captured) {
if (hwnd_) SetTimer(hwnd_, kHintTimerId, kHintDurationMs, nullptr);
}
} else {
ReleaseAllModifiers();
UninstallKeyboardHook();
capture_hint_visible_ = false;
if (hwnd_) KillTimer(hwnd_, kHintTimerId);
Expand Down Expand Up @@ -522,19 +533,24 @@ LRESULT CALLBACK DisplayPanel::LowLevelKeyboardProc(int nCode, WPARAM wp, LPARAM
return 1;
}

// Distinguish left/right modifiers
// Distinguish left/right modifiers for evdev mapping
if (vk == VK_CONTROL) vk = extended ? VK_RCONTROL : VK_LCONTROL;
if (vk == VK_MENU) vk = extended ? VK_RMENU : VK_LMENU;
if (vk == VK_SHIFT) {
vk = (kb->scanCode == 0x36) ? VK_RSHIFT : VK_LSHIFT;
}

// Forward to guest
uint32_t evdev = VkToEvdev(vk);
if (evdev && g_captured_panel->key_cb_) {
g_captured_panel->key_cb_(evdev, pressed);
}

// Swallow the key so the host OS does not act on it
// ESC passes through for long-press exit; everything else swallowed
if (vk == VK_ESCAPE) {
return CallNextHookEx(nullptr, nCode, wp, lp);
}

return 1;
}
return CallNextHookEx(nullptr, nCode, wp, lp);
Expand Down Expand Up @@ -574,6 +590,19 @@ LRESULT CALLBACK DisplayPanel::WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp
case WM_MBUTTONUP:
case WM_MOUSEMOVE:
self->HandleMouse(msg, wp, lp);
// Request WM_MOUSELEAVE to detect mouse exiting the window
{
TRACKMOUSEEVENT tme{ sizeof(tme), TME_LEAVE, hwnd };
TrackMouseEvent(&tme);
}
return 0;

case WM_MOUSELEAVE:
// Mouse left the panel — release all held buttons to prevent stuck state
if (self && self->mouse_buttons_ != 0) {
self->mouse_buttons_ = 0;
if (self->pointer_cb_) self->pointer_cb_(self->last_abs_x_, self->last_abs_y_, 0);
}
return 0;

case WM_MOUSEWHEEL:
Expand All @@ -584,6 +613,7 @@ LRESULT CALLBACK DisplayPanel::WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp
return 0;

case WM_KILLFOCUS:
self->ReleaseAllModifiers();
self->SetCaptured(false);
self->mouse_buttons_ = 0;
return 0;
Expand Down
14 changes: 14 additions & 0 deletions src/manager/ui/win32_display_panel.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
#include <string>
#include <vector>

// Custom messages posted from keyboard hook for fullscreen controls
#define WM_FS_TOGGLE (WM_APP + 10)
#define WM_FS_SWITCH_VM (WM_APP + 11)
#define WM_FS_SHOW_HINT (WM_APP + 12)

#define NOMINMAX
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
Expand Down Expand Up @@ -53,6 +58,11 @@ class DisplayPanel {
// Move/resize the window.
void SetBounds(int x, int y, int w, int h);

// Reparent to a new parent window. Adjusts style bits and fills parent client area.
void Reparent(HWND new_parent);
void SetHintOffsetY(int offset) { hint_offset_y_ = offset; }
void SetFullscreenMode(bool fs) { in_fullscreen_mode_ = fs; }

HWND Handle() const { return hwnd_; }
void SetVisible(bool visible);

Expand Down Expand Up @@ -101,6 +111,10 @@ class DisplayPanel {
HCURSOR custom_cursor_ = nullptr;

float dpi_zoom_factor_ = 1.0f;
int hint_offset_y_ = 0;
bool in_fullscreen_mode_ = false;
DWORD64 esc_press_tick_ = 0;
UINT_PTR esc_exit_timer_ = 0;

KeyEventCallback key_cb_;
PointerEventCallback pointer_cb_;
Expand Down
Loading
Loading