Skip to content

Conversation

@lidaobing
Copy link
Member

@lidaobing lidaobing commented Jan 30, 2026

Summary by Sourcery

Add configurable status icon display modes and wire them through application settings, persistence, and app indicator implementations.

New Features:

  • Introduce a status icon mode enumeration supporting disabling, normal, and blinking status icon behaviors.
  • Expose a status icon mode selector in system settings and persist the chosen mode in program configuration.
  • Apply the configured status icon mode to app indicators on Linux and macOS, including the ability to hide the status icon entirely.

Enhancements:

  • Ensure app indicator unread-count handling respects the selected status icon mode and avoids updating when the icon is disabled.

@sourcery-ai
Copy link

sourcery-ai bot commented Jan 30, 2026

Reviewer's Guide

Adds configurable status icon behavior (none/normal/blinking) across the UI, core configuration, and app indicator implementations, including persistence and runtime updates when settings change.

Sequence diagram for applying status icon mode on startup and config change

sequenceDiagram
  actor User
  participant DataSettings
  participant ProgramData
  participant Application
  participant IptuxAppIndicator as AppIndicator

  rect rgb(230,230,250)
    Application->>ProgramData: ReadProgData()
    Note right of ProgramData: status_icon_mode_ loaded
    Application->>IptuxAppIndicator: IptuxAppIndicator(action_group)
    Application->>IptuxAppIndicator: SetMode(StatusIconMode(ProgramData.statusIconMode()))
    IptuxAppIndicator->>IptuxAppIndicator: priv.mode = mode
    alt mode == STATUS_ICON_MODE_NONE
      IptuxAppIndicator->>IptuxAppIndicator: hide or deactivate status icon
    else mode != STATUS_ICON_MODE_NONE
      IptuxAppIndicator->>IptuxAppIndicator: SetUnreadCount(priv.unreadCount)
    end
  end

  rect rgb(220,255,220)
    User->>DataSettings: Open system settings
    DataSettings->>ProgramData: SetSystemValue() uses statusIconMode()
    User->>DataSettings: Change status icon mode
    User->>DataSettings: Confirm settings
    DataSettings->>ProgramData: setStatusIconMode(new_value)
    DataSettings->>ProgramData: ObtainSystemValue(false)
    ProgramData->>ProgramData: WriteProgData() persists status_icon_mode_
    Application->>Application: onConfigChanged()
    Application->>ProgramData: statusIconMode()
    Application->>IptuxAppIndicator: SetMode(StatusIconMode(ProgramData.statusIconMode()))
    IptuxAppIndicator->>IptuxAppIndicator: update visibility and attention based on mode
  end
Loading

Class diagram for status icon mode integration

classDiagram
  class StatusIconMode {
    <<enumeration>>
    STATUS_ICON_MODE_NONE
    STATUS_ICON_MODE_NORMAL
    STATUS_ICON_MODE_BLINKING
  }

  class ProgramData {
    +bool proof_shared
    +bool hide_taskbar_when_main_window_iconified_
    +bool need_restart_
    +uint8_t status_icon_mode_ : 2
    +void WriteProgData()
    +void ReadProgData()
    +bool isHideTaskbarWhenMainWindowIconified() const
    +int statusIconMode() const
    +void setStatusIconMode(int value)
  }

  class DataSettings {
    +GtkWidget* CreateSystem()
    +void SetSystemValue()
    +string ObtainSystemValue(bool dryrun)
    -GtkWidget* status_icon_mode_combo_widget
  }

  class Application {
    +shared_ptr~IptuxAppIndicator~ app_indicator
    +shared_ptr~ProgramData~ data
    +bool enable_app_indicator_
    +static void onStartup(Application self)
    +void onConfigChanged()
  }

  class IptuxAppIndicator {
    +IptuxAppIndicator(GActionGroup* action_group)
    +void SetUnreadCount(int count)
    +void SetMode(StatusIconMode mode)
    +sigc::signal~void~ sigActivateMainWindow
    -IptuxAppIndicatorPrivate* priv
  }

  class IptuxAppIndicatorPrivate {
    +IptuxAppIndicator* owner
    +void* indicator
    +GtkBuilder* menuBuilder
    +StatusIconMode mode
    +int unreadCount
    +static void onScrollEvent(IptuxAppIndicatorPrivate* self)
  }

  StatusIconMode <.. ProgramData : uses
  StatusIconMode <.. IptuxAppIndicator : uses
  ProgramData o-- StatusIconMode : status_icon_mode_
  Application --> ProgramData : reads_statusIconMode
  Application --> IptuxAppIndicator : configures_mode
  DataSettings --> ProgramData : get_set_statusIconMode
  IptuxAppIndicator *-- IptuxAppIndicatorPrivate : composition
Loading

File-Level Changes

Change Details Files
Introduce status icon mode enum and persist it in program configuration.
  • Add StatusIconMode enum with NONE, NORMAL, and BLINKING values
  • Extend ProgramData with a 2‑bit status_icon_mode_ field, getter, and setter
  • Read/write status_icon_mode to configuration with default NORMAL
src/api/iptux-core/StatusIconMode.h
src/api/iptux-core/ProgramData.h
src/iptux-core/ProgramData.cpp
Expose status icon mode as a system setting in the GTK settings dialog.
  • Define static model data and columns for status icon modes
  • Add a status icon combo box to the System settings grid when HAVE_STATUS_ICON is defined
  • Initialize the combo box from ProgramData::statusIconMode in SetSystemValue
  • Store the selected mode back into ProgramData via ObtainSystemValue
src/iptux/DataSettings.cpp
Wire status icon mode into app indicator implementations and application lifecycle.
  • Track current StatusIconMode and unreadCount in IptuxAppIndicator private data on Linux and macOS
  • Prevent icon updates and hide indicator/status item when mode is NONE, restoring behavior via SetUnreadCount when non‑NONE
  • Expose SetMode(StatusIconMode) on IptuxAppIndicator and provide a no‑op implementation in the dummy variant
  • Initialize app indicator mode from ProgramData on startup and update it when configuration changes
src/iptux/AppIndicator.h
src/iptux/AppIndicator.cpp
src/iptux/AppIndicatorMac.mm
src/iptux/AppIndicatorDummy.cpp
src/iptux/Application.cpp

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • When reading the selected status icon mode (std::stoi(gtk_combo_box_get_active_id(...))), handle the case where gtk_combo_box_get_active_id returns nullptr or a non-integer string to avoid potential exceptions/crashes and fall back to a sensible default.
  • Consider changing ProgramData::statusIconMode() and setStatusIconMode() to use the StatusIconMode enum instead of int, and keeping the internal storage typed accordingly, to make invalid values harder to introduce and better reflect intent.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- When reading the selected status icon mode (`std::stoi(gtk_combo_box_get_active_id(...))`), handle the case where `gtk_combo_box_get_active_id` returns `nullptr` or a non-integer string to avoid potential exceptions/crashes and fall back to a sensible default.
- Consider changing `ProgramData::statusIconMode()` and `setStatusIconMode()` to use the `StatusIconMode` enum instead of `int`, and keeping the internal storage typed accordingly, to make invalid values harder to introduce and better reflect intent.

## Individual Comments

### Comment 1
<location> `src/iptux/DataSettings.cpp:1018-1019` </location>
<code_context>
+#if HAVE_STATUS_ICON
+  widget = GTK_WIDGET(
+      g_datalist_get_data(&widset, "status-icon-mode-combo-widget"));
+  progdt->setStatusIconMode(
+      std::stoi(gtk_combo_box_get_active_id(GTK_COMBO_BOX(widget))));
 #endif
   return oss.str();
</code_context>

<issue_to_address>
**issue (bug_risk):** Guard against null or non-numeric IDs from `gtk_combo_box_get_active_id` before calling `std::stoi`.

`gtk_combo_box_get_active_id` may return `NULL` or a non‑numeric string, causing `std::stoi` to crash or throw. Please check for `nullptr` and validate the ID before converting (e.g., digit check + `std::from_chars` or `std::stoi` in a small try/catch), and fall back to a safe default mode if parsing fails.
</issue_to_address>

### Comment 2
<location> `src/api/iptux-core/ProgramData.h:114` </location>
<code_context>
   uint8_t proof_shared : 1;
   uint8_t hide_taskbar_when_main_window_iconified_ : 1;
   uint8_t need_restart_ : 1;
+  uint8_t status_icon_mode_ : 2;

  private:
</code_context>

<issue_to_address>
**suggestion:** Storing `StatusIconMode` in a 2‑bit field is fragile if more modes are added or if config values are out of range.

With only 2 bits, any value >3 (future enum values or bad config data) will be silently truncated when stored. To avoid mangling unexpected values, either clearly document that `StatusIconMode` is limited to 0–3, or store it as a full `uint8_t` and clamp on read/write (e.g., in `ReadProgData` / `setStatusIconMode`).

Suggested implementation:

```c
  uint8_t proof_shared : 1;
  uint8_t hide_taskbar_when_main_window_iconified_ : 1;
  uint8_t need_restart_ : 1;
  // store StatusIconMode as a full byte; value is validated/clamped in setter / config loading
  uint8_t status_icon_mode_;

```

To fully implement the behavior described in your review comment, the following updates are also needed in the corresponding `.cpp` file(s):

1. In the implementation of `ProgramData::setStatusIconMode(int value)`:
   - Clamp or validate the incoming value before assigning to `status_icon_mode_`, e.g.:
     - if `StatusIconMode` is an enum with values 0–3, something like:
       ```cpp
       value = std::clamp(value, 0, 3);
       status_icon_mode_ = static_cast<uint8_t>(value);
       ```
     - or explicitly handle invalid values by falling back to a default mode.

2. In the implementation of `ProgramData::statusIconMode() const`:
   - Optionally ensure the returned value is in the valid range (in case older persisted config had invalid data), e.g.:
     ```cpp
     int mode = static_cast<int>(status_icon_mode_);
     // clamp or map invalid values to a default
     if (mode < 0 || mode > 3) {
         mode = /* default valid mode */;
     }
     return mode;
     ```

3. In `ReadProgData` (or whichever function loads the config):
   - When reading the status icon mode from configuration:
     - Either call `setStatusIconMode(loaded_value)` so the clamping logic is centralized, or
     - Clamp/validate `loaded_value` before assigning to `status_icon_mode_`.

These changes ensure that we do not silently truncate values >3 when storing them and that any out-of-range or future enum values are handled in a controlled way.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@lidaobing lidaobing requested a review from Copilot January 30, 2026 05:08
@github-actions
Copy link

Test Results

69 tests  ±0   69 ✅ ±0   2s ⏱️ -1s
32 suites ±0    0 💤 ±0 
 1 files   ±0    0 ❌ ±0 

Results for commit 861d8d9. ± Comparison against base commit 8c9828c.

@codecov
Copy link

codecov bot commented Jan 30, 2026

Codecov Report

❌ Patch coverage is 77.50000% with 9 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (main@8c9828c). Learn more about missing BASE report.
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
src/iptux/AppIndicator.cpp 0.00% 7 Missing ⚠️
src/iptux/Application.cpp 33.33% 2 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main     #713   +/-   ##
=======================================
  Coverage        ?   52.11%           
=======================================
  Files           ?       64           
  Lines           ?     8641           
  Branches        ?        0           
=======================================
  Hits            ?     4503           
  Misses          ?     4138           
  Partials        ?        0           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@lidaobing lidaobing merged commit 60cd410 into main Jan 30, 2026
19 of 24 checks passed
@lidaobing lidaobing deleted the bf_hide_status_icon branch January 30, 2026 05:11
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a new user-configurable status icon mode setting to iptux, allowing users to choose between no status icon, normal status icon, or blinking status icon (though blinking is not yet implemented). The feature includes UI preferences, configuration persistence, and platform-specific implementations for Linux and macOS app indicators.

Changes:

  • Introduces StatusIconMode enum with three modes: NONE, NORMAL, and BLINKING
  • Adds UI preferences in DataSettings for selecting status icon mode with proper localization
  • Implements configuration persistence through ProgramData with storage in IptuxConfig
  • Updates app indicator implementations on Linux and macOS to respect the selected mode, including hiding the icon when NONE is selected

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
src/api/iptux-core/StatusIconMode.h Defines the StatusIconMode enum with three values
src/api/iptux-core/ProgramData.h Adds statusIconMode getter/setter methods and 2-bit bitfield storage
src/iptux-core/ProgramData.cpp Implements configuration read/write for status icon mode with default value
src/iptux/DataSettings.cpp Adds GTK+ combo box UI widget for selecting status icon mode in preferences
src/iptux/Application.cpp Initializes app indicator mode on startup and updates it when configuration changes
src/iptux/AppIndicator.h Adds SetMode method to the IptuxAppIndicator interface
src/iptux/AppIndicator.cpp Implements status icon mode handling for Linux using ayatana-appindicator
src/iptux/AppIndicatorMac.mm Implements status icon mode handling for macOS using NSStatusItem visibility
src/iptux/AppIndicatorDummy.cpp Adds dummy SetMode implementation for platforms without status icon support
src/config.h.in Defines HAVE_STATUS_ICON macro based on platform capabilities
NEWS Documents the new feature in version 0.10.0 release notes

if (self.enable_app_indicator_) {
self.app_indicator =
make_shared<IptuxAppIndicator>(G_ACTION_GROUP(self.app));
self.app_indicator->SetMode(StatusIconMode(self.data->statusIconMode()));
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using a C-style cast for StatusIconMode conversion. Consider using static_cast for better type safety and to make the conversion explicit. This would help catch potential type mismatches at compile time.

Suggested change
self.app_indicator->SetMode(StatusIconMode(self.data->statusIconMode()));
self.app_indicator->SetMode(
static_cast<StatusIconMode>(self.data->statusIconMode()));

Copilot uses AI. Check for mistakes.
add_accelerator(app, "win.send_message", "<Primary>Return");
}
if (app_indicator) {
app_indicator->SetMode(StatusIconMode(data->statusIconMode()));
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using a C-style cast for StatusIconMode conversion. Consider using static_cast for better type safety and to make the conversion explicit.

Suggested change
app_indicator->SetMode(StatusIconMode(data->statusIconMode()));
app_indicator->SetMode(
static_cast<StatusIconMode>(data->statusIconMode()));

Copilot uses AI. Check for mistakes.
Comment on lines +1018 to +1019
progdt->setStatusIconMode(
std::stoi(gtk_combo_box_get_active_id(GTK_COMBO_BOX(widget))));
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The value retrieved from the combo box is parsed using std::stoi without validation. If the combo box somehow contains an invalid or out-of-range value (e.g., not 0, 1, or 2), this could result in an invalid StatusIconMode being stored. Consider validating the parsed integer is within the valid range (0-2) before calling setStatusIconMode, or add a bounds check in the setStatusIconMode method itself.

Copilot uses AI. Check for mistakes.
return status_icon_mode_;
}

void ProgramData::setStatusIconMode(int value) {
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The setStatusIconMode method accepts an int parameter and directly assigns it to the 2-bit bitfield status_icon_mode_ without validation. Since the bitfield can only hold values 0-3, but only 0-2 are valid StatusIconMode values, values outside this range could lead to unexpected behavior. Consider either: 1) changing the parameter type to StatusIconMode to enforce type safety, or 2) adding validation to clamp or reject invalid values.

Suggested change
void ProgramData::setStatusIconMode(int value) {
void ProgramData::setStatusIconMode(int value) {
if (value < 0) {
value = 0;
} else if (value > 2) {
value = 2;
}

Copilot uses AI. Check for mistakes.
Comment on lines +57 to +58
int statusIconMode() const;
void setStatusIconMode(int value);
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The API declares statusIconMode() as returning int and setStatusIconMode() as accepting int, rather than using the StatusIconMode enum type. For better type safety and API clarity, consider changing these to use StatusIconMode directly.

Suggested change
int statusIconMode() const;
void setStatusIconMode(int value);
StatusIconMode statusIconMode() const;
void setStatusIconMode(StatusIconMode value);

Copilot uses AI. Check for mistakes.
} kStatusIconModes[] = {
{STATUS_ICON_MODE_NONE, N_("No status icon")},
{STATUS_ICON_MODE_NORMAL, N_("Normal status icon")},
{STATUS_ICON_MODE_BLINKING, N_("Blinking status icon")},
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The STATUS_ICON_MODE_BLINKING option is presented to users in the UI, but there is no implementation that actually handles blinking behavior. Both the Linux (AppIndicator.cpp) and macOS (AppIndicatorMac.mm) implementations only check for STATUS_ICON_MODE_NONE and treat all other modes (including BLINKING) the same as NORMAL. Either implement the blinking functionality or remove this option from the UI until it's supported.

Suggested change
{STATUS_ICON_MODE_BLINKING, N_("Blinking status icon")},

Copilot uses AI. Check for mistakes.
bool IsUsingBlacklist() const;
bool IsFilterFileShareRequest() const;
bool isHideTaskbarWhenMainWindowIconified() const;
int statusIconMode() const;
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naming convention inconsistency: the getter uses camelCase (statusIconMode) while the setter uses camelCase (setStatusIconMode), but the similar method isHideTaskbarWhenMainWindowIconified uses "is" prefix for the getter. While "is" is typically used for boolean getters, for consistency with other non-boolean getters in this class (like port(), which doesn't use "get"), consider using getStatusIconMode() or keeping statusIconMode() but ensuring consistency throughout.

Suggested change
int statusIconMode() const;
int statusIconMode() const;
int getStatusIconMode() const { return statusIconMode(); }

Copilot uses AI. Check for mistakes.
Comment on lines +250 to +255
int ProgramData::statusIconMode() const {
return status_icon_mode_;
}

void ProgramData::setStatusIconMode(int value) {
status_icon_mode_ = value;
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The statusIconMode getter returns int instead of StatusIconMode. For better type safety and API consistency, consider returning StatusIconMode directly. This would make the API clearer and prevent misuse. The setStatusIconMode method could similarly be updated to accept StatusIconMode instead of int.

Suggested change
int ProgramData::statusIconMode() const {
return status_icon_mode_;
}
void ProgramData::setStatusIconMode(int value) {
status_icon_mode_ = value;
StatusIconMode ProgramData::statusIconMode() const {
return static_cast<StatusIconMode>(status_icon_mode_);
}
void ProgramData::setStatusIconMode(StatusIconMode value) {
status_icon_mode_ = static_cast<decltype(status_icon_mode_)>(value);

Copilot uses AI. Check for mistakes.
proof_shared = config->GetBool("proof_shared");
hide_taskbar_when_main_window_iconified_ =
config->GetBool("hide_taskbar_when_main_window_iconified");
status_icon_mode_ = config->GetInt("status_icon_mode", STATUS_ICON_MODE_NORMAL);
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new status icon mode feature lacks test coverage. Consider adding tests to verify: 1) the status icon mode is correctly saved and loaded from configuration, 2) the default value is STATUS_ICON_MODE_NORMAL when not set, and 3) invalid values are handled appropriately. The existing ProgramDataTest.cpp file demonstrates the testing pattern for configuration persistence.

Copilot uses AI. Check for mistakes.
proof_shared = config->GetBool("proof_shared");
hide_taskbar_when_main_window_iconified_ =
config->GetBool("hide_taskbar_when_main_window_iconified");
status_icon_mode_ = config->GetInt("status_icon_mode", STATUS_ICON_MODE_NORMAL);
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When reading the status_icon_mode_ from configuration, there is no validation to ensure the value is within the valid range (0-2). If the configuration file contains an invalid value (e.g., 3 or negative), it will be stored directly into the 2-bit bitfield, potentially causing unexpected behavior. Consider adding validation similar to how send_message_retry_in_us is validated on lines 153-155, clamping the value to the valid range or resetting to the default if invalid.

Suggested change
status_icon_mode_ = config->GetInt("status_icon_mode", STATUS_ICON_MODE_NORMAL);
int status_icon_mode =
config->GetInt("status_icon_mode", STATUS_ICON_MODE_NORMAL);
if (status_icon_mode < 0 || status_icon_mode > 2) {
status_icon_mode = STATUS_ICON_MODE_NORMAL;
}
status_icon_mode_ = status_icon_mode;

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants