# How ChatMCP Handles Window Management on Desktop Platforms Using `window_manager`

> Discover how ChatMCP manages desktop windows using window_manager. Explore frameless windows, toolbar dragging, and custom controls for minimize, maximize, and close functions.

- Repository: [刀刀/chatmcp](https://github.com/daodao97/chatmcp)
- Tags: how-to-guide
- Published: 2026-02-28

---

**ChatMCP uses the `window_manager` Flutter plugin to initialize frameless windows with custom sizes, enables toolbar dragging via `DragToMoveArea`, and exposes minimize, maximize, and close controls through platform-guarded UI widgets.**

ChatMCP is an open-source Flutter-based chat client that leverages the `window_manager` plugin to deliver native desktop window experiences on Windows, macOS, and Linux. When running on desktop platforms, the application hijacks the native title bar, implements custom draggable toolbars, and provides programmatic control over window states. This article examines the exact implementation details found in the repository's Dart source files.

## Desktop Platform Detection and Initialization

Before invoking any desktop-specific APIs, ChatMCP determines the current platform using utility constants defined in `lib/utils/platform.dart`.

### Checking for Desktop Environments

The application defines a boolean flag that evaluates to true only when running natively on Windows, macOS, or Linux:

```dart
final bool kIsDesktop = !kIsWeb && (kIsLinux || kIsWindows || kIsMacOS);

```

This `kIsDesktop` constant guards all desktop-specific logic throughout the UI, ensuring mobile and web builds never invoke unsupported native methods.

### Early Initialization in `main.dart`

When `kIsDesktop` is true, `lib/main.dart` initializes the window manager before any widgets render:

```dart
if (kIsDesktop) {
  await wm.windowManager.ensureInitialized();
  
  final wm.WindowOptions windowOptions = wm.WindowOptions(
    size: Size(1200, 800),
    minimumSize: Size(400, 600),
    center: true,
    backgroundColor: Colors.transparent,
    titleBarStyle: wm.TitleBarStyle.hidden,
    windowButtonVisibility: true,
  );

  await wm.windowManager.waitUntilReadyToShow(windowOptions, () async {
    try {
      await wm.windowManager.show();
      await wm.windowManager.focus();
    } catch (e) {
      Logger.root.warning('Window initialization error: $e');
    }
  });
}

```

The `ensureInitialized()` method loads the native bindings, while `waitUntilReadyToShow` delays the window presentation until the specified options are applied.

## Configuring Frameless Window Behavior

ChatMCP opts for a custom title bar by hiding the native OS chrome and implementing its own controls.

### WindowOptions and Hidden Title Bars

The `WindowOptions` constructor in `main.dart` configures several critical parameters:

- **`size: Size(1200, 800)`** — Sets the default window dimensions on launch.
- **`minimumSize: Size(400, 600)`** — Prevents the user from resizing below usable thresholds.
- **`titleBarStyle: wm.TitleBarStyle.hidden`** — Removes the native title bar, allowing Flutter to draw a custom toolbar.
- **`backgroundColor: Colors.transparent`** — Enables seamless blending with custom frame designs.

### Showing and Focusing the Window

Inside the `waitUntilReadyToShow` callback, the application explicitly calls `wm.windowManager.show()` to make the window visible and `wm.windowManager.focus()` to bring it to the foreground. A brief delay ensures the native window completes its setup before the UI appears.

## Implementing Draggable Custom Chrome

Without a native title bar, users need an alternative way to move the window. ChatMCP solves this by making the top toolbar a draggable zone.

### DragToMoveArea for Frameless Windows

In `lib/page/layout/widgets/top_toolbar.dart`, the toolbar conditionally wraps its content with `DragToMoveArea`:

```dart
child: kIsDesktop
    ? wm.DragToMoveArea(child: _buildToolbarContent(context))
    : _buildToolbarContent(context),

```

The `DragToMoveArea` widget intercepts mouse drag events and forwards them to the native window manager, allowing users to reposition the frameless window by dragging the custom toolbar.

### Double-Tap Maximization Gesture

The toolbar also supports a double-tap gesture to toggle between maximized and restored states:

```dart
onDoubleTap: () async {
  if (kIsDesktop) {
    bool isMaximized = await wm.windowManager.isMaximized();
    if (isMaximized) {
      await wm.windowManager.unmaximize();
    } else {
      await wm.windowManager.maximize();
    }
  }
},

```

This logic checks the current window state asynchronously before invoking the appropriate API.

## Custom Window Control Buttons

Since the native title bar is hidden, ChatMCP provides its own minimize, maximize, and close buttons in `lib/page/layout/widgets/window_controls.dart`.

### The WindowControls Widget Implementation

The widget defines three control buttons with distinct behaviors:

**Minimize Button:**

```dart
_WindowButton(
  icon: Icons.remove,
  onPressed: () async => await wm.windowManager.minimize(),
  tooltip: l10n.minimize,
),

```

**Maximize/Restore Toggle:**

```dart
_WindowButton(
  icon: Icons.crop_square,
  onPressed: () async {
    bool isMaximized = await wm.windowManager.isMaximized();
    if (isMaximized) {
      await wm.windowManager.unmaximize();
    } else {
      await wm.windowManager.maximize();
    }
  },
  tooltip: l10n.maximize,
),

```

**Close Button:**

```dart
_WindowButton(
  icon: Icons.close,
  onPressed: () async => await wm.windowManager.close(),
  tooltip: l10n.close,
  isCloseButton: true,
),

```

### State-Aware Maximize and Restore

The maximize button dynamically changes behavior based on the window's current state. When `isMaximized()` returns true, the button calls `unmaximize()` to restore the previous size; otherwise, it calls `maximize()` to fill the screen.

## Additional Window Management Techniques

Beyond the core setup, `window_manager` enables runtime manipulation of window properties.

### Runtime Window Resizing

Developers can adjust window dimensions programmatically after initialization:

```dart
import 'package:window_manager/window_manager.dart' as wm;

Future<void> resizeWindow() async {
  await wm.windowManager.setSize(const Size(1600, 900));
}

```

### Taskbar Visibility Control

To hide the window from the taskbar or dock:

```dart
await wm.windowManager.setSkipTaskbar(true);

```

### Listening for Window Events

The plugin supports event listeners for state changes:

```dart
wm.windowManager.addListener(MyWindowListener());

class MyWindowListener extends wm.WindowListener {
  @override
  void onWindowFocus() {
    debugPrint('Window gained focus');
  }
  
  @override
  void onWindowBlur() {
    debugPrint('Window lost focus');
  }
}

```

## Summary

- ChatMCP initializes `window_manager` early in `lib/main.dart` only when `kIsDesktop` is true, using `WindowOptions` to configure a 1200×800 frameless window centered on screen.
- The `DragToMoveArea` widget wraps the toolbar in `lib/page/layout/widgets/top_toolbar.dart`, enabling users to drag the frameless window by its custom chrome.
- Double-tapping the toolbar checks `isMaximized()` to toggle asynchronously between `maximize()` and `unmaximize()` states.
- The `WindowControls` widget provides explicit minimize, maximize/restore, and close buttons that invoke corresponding `window_manager` APIs.
- Runtime window manipulation leverages `setSize()`, `setSkipTaskbar()`, and event listeners for dynamic desktop window behavior.

## Frequently Asked Questions

### Why does ChatMCP hide the native title bar on desktop?

ChatMCP hides the native title bar using `TitleBarStyle.hidden` in `WindowOptions` to implement a custom frameless design that matches the Flutter UI theme. This allows the application to draw its own gradient backgrounds and custom toolbar widgets while maintaining native window controls through the `window_manager` API.

### How does ChatMCP allow dragging a window without a title bar?

The application wraps the toolbar content with the `DragToMoveArea` widget from the `window_manager` package, as seen in `lib/page/layout/widgets/top_toolbar.dart`. This widget forwards mouse drag events directly to the native window manager, enabling standard window repositioning behavior despite the absence of native chrome.

### Can I change the default window size in ChatMCP?

Yes, the default size is defined in `lib/main.dart` within the `WindowOptions` constructor as `Size(1200, 800)`. You can modify these dimensions before building, or call `wm.windowManager.setSize()` at runtime to dynamically resize the window based on user preferences or screen constraints.

### What happens if `window_manager` fails to initialize?

The initialization logic in `lib/main.dart` wraps the `show()` and `focus()` calls inside a try-catch block that logs warnings via `Logger.root.warning`. This defensive programming ensures that if the native window manager fails to bind, the application continues running rather than crashing, though the window may appear with default system styling.