Merge pull request #2145 from atom/gtk-message-box
Use GtkMessageBox for dialog.showMessageBox on Linux
This commit is contained in:
commit
083d0b8b60
7 changed files with 216 additions and 82 deletions
|
@ -9,7 +9,7 @@ fileDialogProperties =
|
||||||
multiSelections: 1 << 2
|
multiSelections: 1 << 2
|
||||||
createDirectory: 1 << 3
|
createDirectory: 1 << 3
|
||||||
|
|
||||||
messageBoxTypes = ['none', 'info', 'warning']
|
messageBoxTypes = ['none', 'info', 'warning', 'error', 'question']
|
||||||
|
|
||||||
parseArgs = (window, options, callback) ->
|
parseArgs = (window, options, callback) ->
|
||||||
unless window is null or window?.constructor is BrowserWindow
|
unless window is null or window?.constructor is BrowserWindow
|
||||||
|
|
|
@ -4,49 +4,18 @@
|
||||||
|
|
||||||
#include "atom/browser/ui/file_dialog.h"
|
#include "atom/browser/ui/file_dialog.h"
|
||||||
|
|
||||||
#include <gdk/gdk.h>
|
|
||||||
#include <gdk/gdkx.h>
|
|
||||||
#include <gtk/gtk.h>
|
|
||||||
|
|
||||||
// This conflicts with mate::Converter,
|
|
||||||
#undef True
|
|
||||||
#undef False
|
|
||||||
// and V8.
|
|
||||||
#undef None
|
|
||||||
|
|
||||||
#include "atom/browser/native_window.h"
|
#include "atom/browser/native_window.h"
|
||||||
#include "base/callback.h"
|
#include "base/callback.h"
|
||||||
#include "base/files/file_util.h"
|
#include "base/files/file_util.h"
|
||||||
#include "base/strings/string_util.h"
|
#include "base/strings/string_util.h"
|
||||||
#include "chrome/browser/ui/libgtk2ui/gtk2_signal.h"
|
#include "chrome/browser/ui/libgtk2ui/gtk2_signal.h"
|
||||||
#include "ui/aura/window.h"
|
#include "chrome/browser/ui/libgtk2ui/gtk2_util.h"
|
||||||
#include "ui/aura/window_tree_host.h"
|
|
||||||
#include "ui/views/widget/desktop_aura/x11_desktop_handler.h"
|
#include "ui/views/widget/desktop_aura/x11_desktop_handler.h"
|
||||||
|
|
||||||
namespace file_dialog {
|
namespace file_dialog {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
const char kAuraTransientParent[] = "aura-transient-parent";
|
|
||||||
|
|
||||||
void SetGtkTransientForAura(GtkWidget* dialog, aura::Window* parent) {
|
|
||||||
if (!parent || !parent->GetHost())
|
|
||||||
return;
|
|
||||||
|
|
||||||
gtk_widget_realize(dialog);
|
|
||||||
GdkWindow* gdk_window = gtk_widget_get_window(dialog);
|
|
||||||
|
|
||||||
// TODO(erg): Check to make sure we're using X11 if wayland or some other
|
|
||||||
// display server ever happens. Otherwise, this will crash.
|
|
||||||
XSetTransientForHint(GDK_WINDOW_XDISPLAY(gdk_window),
|
|
||||||
GDK_WINDOW_XID(gdk_window),
|
|
||||||
parent->GetHost()->GetAcceleratedWidget());
|
|
||||||
|
|
||||||
// We also set the |parent| as a property of |dialog|, so that we can unlink
|
|
||||||
// the two later.
|
|
||||||
g_object_set_data(G_OBJECT(dialog), kAuraTransientParent, parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Makes sure that .jpg also shows .JPG.
|
// Makes sure that .jpg also shows .JPG.
|
||||||
gboolean FileFilterCaseInsensitive(const GtkFileFilterInfo* file_info,
|
gboolean FileFilterCaseInsensitive(const GtkFileFilterInfo* file_info,
|
||||||
std::string* file_extension) {
|
std::string* file_extension) {
|
||||||
|
@ -65,7 +34,7 @@ class FileChooserDialog {
|
||||||
const std::string& title,
|
const std::string& title,
|
||||||
const base::FilePath& default_path,
|
const base::FilePath& default_path,
|
||||||
const Filters& filters)
|
const Filters& filters)
|
||||||
: dialog_scope_(new atom::NativeWindow::DialogScope(parent_window)) {
|
: dialog_scope_(parent_window) {
|
||||||
const char* confirm_text = GTK_STOCK_OK;
|
const char* confirm_text = GTK_STOCK_OK;
|
||||||
if (action == GTK_FILE_CHOOSER_ACTION_SAVE)
|
if (action == GTK_FILE_CHOOSER_ACTION_SAVE)
|
||||||
confirm_text = GTK_STOCK_SAVE;
|
confirm_text = GTK_STOCK_SAVE;
|
||||||
|
@ -81,7 +50,7 @@ class FileChooserDialog {
|
||||||
NULL);
|
NULL);
|
||||||
if (parent_window) {
|
if (parent_window) {
|
||||||
gfx::NativeWindow window = parent_window->GetNativeWindow();
|
gfx::NativeWindow window = parent_window->GetNativeWindow();
|
||||||
SetGtkTransientForAura(dialog_, window);
|
libgtk2ui::SetGtkTransientForAura(dialog_, window);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action == GTK_FILE_CHOOSER_ACTION_SAVE)
|
if (action == GTK_FILE_CHOOSER_ACTION_SAVE)
|
||||||
|
@ -162,13 +131,13 @@ class FileChooserDialog {
|
||||||
private:
|
private:
|
||||||
void AddFilters(const Filters& filters);
|
void AddFilters(const Filters& filters);
|
||||||
|
|
||||||
|
atom::NativeWindow::DialogScope dialog_scope_;
|
||||||
|
|
||||||
GtkWidget* dialog_;
|
GtkWidget* dialog_;
|
||||||
|
|
||||||
SaveDialogCallback save_callback_;
|
SaveDialogCallback save_callback_;
|
||||||
OpenDialogCallback open_callback_;
|
OpenDialogCallback open_callback_;
|
||||||
|
|
||||||
scoped_ptr<atom::NativeWindow::DialogScope> dialog_scope_;
|
|
||||||
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(FileChooserDialog);
|
DISALLOW_COPY_AND_ASSIGN(FileChooserDialog);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,9 @@ class NativeWindow;
|
||||||
enum MessageBoxType {
|
enum MessageBoxType {
|
||||||
MESSAGE_BOX_TYPE_NONE = 0,
|
MESSAGE_BOX_TYPE_NONE = 0,
|
||||||
MESSAGE_BOX_TYPE_INFORMATION,
|
MESSAGE_BOX_TYPE_INFORMATION,
|
||||||
MESSAGE_BOX_TYPE_WARNING
|
MESSAGE_BOX_TYPE_WARNING,
|
||||||
|
MESSAGE_BOX_TYPE_ERROR,
|
||||||
|
MESSAGE_BOX_TYPE_QUESTION,
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef base::Callback<void(int code)> MessageBoxCallback;
|
typedef base::Callback<void(int code)> MessageBoxCallback;
|
||||||
|
|
203
atom/browser/ui/message_box_gtk.cc
Normal file
203
atom/browser/ui/message_box_gtk.cc
Normal file
|
@ -0,0 +1,203 @@
|
||||||
|
// Copyright (c) 2015 GitHub, Inc.
|
||||||
|
// Use of this source code is governed by the MIT license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
#include "atom/browser/ui/message_box.h"
|
||||||
|
|
||||||
|
#include "atom/browser/browser.h"
|
||||||
|
#include "atom/browser/native_window.h"
|
||||||
|
#include "base/callback.h"
|
||||||
|
#include "base/strings/string_util.h"
|
||||||
|
#include "base/strings/utf_string_conversions.h"
|
||||||
|
#include "chrome/browser/ui/libgtk2ui/gtk2_signal.h"
|
||||||
|
#include "chrome/browser/ui/libgtk2ui/gtk2_util.h"
|
||||||
|
#include "chrome/browser/ui/libgtk2ui/skia_utils_gtk2.h"
|
||||||
|
#include "ui/views/widget/desktop_aura/x11_desktop_handler.h"
|
||||||
|
|
||||||
|
#define ANSI_FOREGROUND_RED "\x1b[31m"
|
||||||
|
#define ANSI_FOREGROUND_BLACK "\x1b[30m"
|
||||||
|
#define ANSI_TEXT_BOLD "\x1b[1m"
|
||||||
|
#define ANSI_BACKGROUND_GRAY "\x1b[47m"
|
||||||
|
#define ANSI_RESET "\x1b[0m"
|
||||||
|
|
||||||
|
namespace atom {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
class GtkMessageBox {
|
||||||
|
public:
|
||||||
|
GtkMessageBox(NativeWindow* parent_window,
|
||||||
|
MessageBoxType type,
|
||||||
|
const std::vector<std::string>& buttons,
|
||||||
|
const std::string& title,
|
||||||
|
const std::string& message,
|
||||||
|
const std::string& detail,
|
||||||
|
const gfx::ImageSkia& icon)
|
||||||
|
: dialog_scope_(parent_window),
|
||||||
|
cancel_id_(0) {
|
||||||
|
// Create dialog.
|
||||||
|
dialog_ = gtk_message_dialog_new(
|
||||||
|
nullptr, // parent
|
||||||
|
static_cast<GtkDialogFlags>(0), // no flags
|
||||||
|
GetMessageType(type), // type
|
||||||
|
GTK_BUTTONS_NONE, // no buttons
|
||||||
|
"%s", message.c_str());
|
||||||
|
if (!detail.empty())
|
||||||
|
gtk_message_dialog_format_secondary_text(
|
||||||
|
GTK_MESSAGE_DIALOG(dialog_), "%s", detail.c_str());
|
||||||
|
if (!title.empty())
|
||||||
|
gtk_window_set_title(GTK_WINDOW(dialog_), title.c_str());
|
||||||
|
|
||||||
|
// Set dialog's icon.
|
||||||
|
if (!icon.isNull()) {
|
||||||
|
GdkPixbuf* pixbuf = libgtk2ui::GdkPixbufFromSkBitmap(*icon.bitmap());
|
||||||
|
GtkWidget* image = gtk_image_new_from_pixbuf(pixbuf);
|
||||||
|
gtk_message_dialog_set_image(GTK_MESSAGE_DIALOG(dialog_), image);
|
||||||
|
gtk_widget_show(image);
|
||||||
|
g_object_unref(pixbuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add buttons.
|
||||||
|
for (size_t i = 0; i < buttons.size(); ++i) {
|
||||||
|
gtk_dialog_add_button(GTK_DIALOG(dialog_),
|
||||||
|
TranslateToStock(i, buttons[i]),
|
||||||
|
i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parent window.
|
||||||
|
if (parent_window) {
|
||||||
|
gfx::NativeWindow window = parent_window->GetNativeWindow();
|
||||||
|
libgtk2ui::SetGtkTransientForAura(dialog_, window);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~GtkMessageBox() {
|
||||||
|
gtk_widget_destroy(dialog_);
|
||||||
|
}
|
||||||
|
|
||||||
|
GtkMessageType GetMessageType(MessageBoxType type) {
|
||||||
|
switch (type) {
|
||||||
|
case MESSAGE_BOX_TYPE_INFORMATION:
|
||||||
|
return GTK_MESSAGE_INFO;
|
||||||
|
case MESSAGE_BOX_TYPE_WARNING:
|
||||||
|
return GTK_MESSAGE_WARNING;
|
||||||
|
case MESSAGE_BOX_TYPE_QUESTION:
|
||||||
|
return GTK_MESSAGE_QUESTION;
|
||||||
|
case MESSAGE_BOX_TYPE_ERROR:
|
||||||
|
return GTK_MESSAGE_ERROR;
|
||||||
|
default:
|
||||||
|
return GTK_MESSAGE_OTHER;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* TranslateToStock(int id, const std::string& text) {
|
||||||
|
std::string lower = base::StringToLowerASCII(text);
|
||||||
|
if (lower == "cancel") {
|
||||||
|
cancel_id_ = id;
|
||||||
|
return GTK_STOCK_CANCEL;
|
||||||
|
} else if (lower == "no") {
|
||||||
|
cancel_id_ = id;
|
||||||
|
return GTK_STOCK_NO;
|
||||||
|
} else if (lower == "ok") {
|
||||||
|
return GTK_STOCK_OK;
|
||||||
|
} else if (lower == "yes") {
|
||||||
|
return GTK_STOCK_YES;
|
||||||
|
} else {
|
||||||
|
return text.c_str();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Show() {
|
||||||
|
gtk_widget_show_all(dialog_);
|
||||||
|
// We need to call gtk_window_present after making the widgets visible to
|
||||||
|
// make sure window gets correctly raised and gets focus.
|
||||||
|
int time = views::X11DesktopHandler::get()->wm_user_time_ms();
|
||||||
|
gtk_window_present_with_time(GTK_WINDOW(dialog_), time);
|
||||||
|
}
|
||||||
|
|
||||||
|
int RunSynchronous() {
|
||||||
|
gtk_window_set_modal(GTK_WINDOW(dialog_), TRUE);
|
||||||
|
Show();
|
||||||
|
int response = gtk_dialog_run(GTK_DIALOG(dialog_));
|
||||||
|
if (response < 0)
|
||||||
|
return cancel_id_;
|
||||||
|
else
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RunAsynchronous(const MessageBoxCallback& callback) {
|
||||||
|
callback_ = callback;
|
||||||
|
g_signal_connect(dialog_, "delete-event",
|
||||||
|
G_CALLBACK(gtk_widget_hide_on_delete), nullptr);
|
||||||
|
g_signal_connect(dialog_, "response",
|
||||||
|
G_CALLBACK(OnResponseDialogThunk), this);
|
||||||
|
Show();
|
||||||
|
}
|
||||||
|
|
||||||
|
CHROMEGTK_CALLBACK_1(GtkMessageBox, void, OnResponseDialog, int);
|
||||||
|
|
||||||
|
private:
|
||||||
|
atom::NativeWindow::DialogScope dialog_scope_;
|
||||||
|
|
||||||
|
// The id to return when the dialog is closed without pressing buttons.
|
||||||
|
int cancel_id_;
|
||||||
|
|
||||||
|
GtkWidget* dialog_;
|
||||||
|
MessageBoxCallback callback_;
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(GtkMessageBox);
|
||||||
|
};
|
||||||
|
|
||||||
|
void GtkMessageBox::OnResponseDialog(GtkWidget* widget, int response) {
|
||||||
|
gtk_widget_hide_all(dialog_);
|
||||||
|
|
||||||
|
if (response < 0)
|
||||||
|
callback_.Run(cancel_id_);
|
||||||
|
else
|
||||||
|
callback_.Run(response);
|
||||||
|
delete this;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int ShowMessageBox(NativeWindow* parent,
|
||||||
|
MessageBoxType type,
|
||||||
|
const std::vector<std::string>& buttons,
|
||||||
|
const std::string& title,
|
||||||
|
const std::string& message,
|
||||||
|
const std::string& detail,
|
||||||
|
const gfx::ImageSkia& icon) {
|
||||||
|
return GtkMessageBox(parent, type, buttons, title, message, detail,
|
||||||
|
icon).RunSynchronous();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShowMessageBox(NativeWindow* parent,
|
||||||
|
MessageBoxType type,
|
||||||
|
const std::vector<std::string>& buttons,
|
||||||
|
const std::string& title,
|
||||||
|
const std::string& message,
|
||||||
|
const std::string& detail,
|
||||||
|
const gfx::ImageSkia& icon,
|
||||||
|
const MessageBoxCallback& callback) {
|
||||||
|
(new GtkMessageBox(parent, type, buttons, title, message, detail,
|
||||||
|
icon))->RunAsynchronous(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShowErrorBox(const base::string16& title, const base::string16& content) {
|
||||||
|
if (Browser::Get()->is_ready()) {
|
||||||
|
GtkMessageBox(nullptr, MESSAGE_BOX_TYPE_ERROR, { "OK" }, "Error",
|
||||||
|
base::UTF16ToUTF8(title).c_str(),
|
||||||
|
base::UTF16ToUTF8(content).c_str(),
|
||||||
|
gfx::ImageSkia()).RunSynchronous();
|
||||||
|
} else {
|
||||||
|
fprintf(stderr,
|
||||||
|
ANSI_TEXT_BOLD ANSI_BACKGROUND_GRAY
|
||||||
|
ANSI_FOREGROUND_RED "%s\n"
|
||||||
|
ANSI_FOREGROUND_BLACK "%s"
|
||||||
|
ANSI_RESET "\n",
|
||||||
|
base::UTF16ToUTF8(title).c_str(),
|
||||||
|
base::UTF16ToUTF8(content).c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace atom
|
|
@ -4,10 +4,6 @@
|
||||||
|
|
||||||
#include "atom/browser/ui/message_box.h"
|
#include "atom/browser/ui/message_box.h"
|
||||||
|
|
||||||
#if defined(USE_X11)
|
|
||||||
#include <gtk/gtk.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "atom/browser/native_window.h"
|
#include "atom/browser/native_window.h"
|
||||||
#include "base/callback.h"
|
#include "base/callback.h"
|
||||||
#include "base/message_loop/message_loop.h"
|
#include "base/message_loop/message_loop.h"
|
||||||
|
@ -26,21 +22,10 @@
|
||||||
#include "ui/views/widget/widget_delegate.h"
|
#include "ui/views/widget/widget_delegate.h"
|
||||||
#include "ui/wm/core/shadow_types.h"
|
#include "ui/wm/core/shadow_types.h"
|
||||||
|
|
||||||
#if defined(USE_X11)
|
|
||||||
#include "atom/browser/browser.h"
|
|
||||||
#include "ui/views/window/native_frame_view.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(OS_WIN)
|
#if defined(OS_WIN)
|
||||||
#include "ui/base/win/message_box_win.h"
|
#include "ui/base/win/message_box_win.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define ANSI_FOREGROUND_RED "\x1b[31m"
|
|
||||||
#define ANSI_FOREGROUND_BLACK "\x1b[30m"
|
|
||||||
#define ANSI_TEXT_BOLD "\x1b[1m"
|
|
||||||
#define ANSI_BACKGROUND_GRAY "\x1b[47m"
|
|
||||||
#define ANSI_RESET "\x1b[0m"
|
|
||||||
|
|
||||||
namespace atom {
|
namespace atom {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
@ -276,13 +261,8 @@ ui::ModalType MessageDialog::GetModalType() const {
|
||||||
|
|
||||||
views::NonClientFrameView* MessageDialog::CreateNonClientFrameView(
|
views::NonClientFrameView* MessageDialog::CreateNonClientFrameView(
|
||||||
views::Widget* widget) {
|
views::Widget* widget) {
|
||||||
if (!parent_) {
|
if (!parent_)
|
||||||
#if defined(USE_X11)
|
|
||||||
return new views::NativeFrameView(widget);
|
|
||||||
#else
|
|
||||||
return NULL;
|
return NULL;
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a bubble style frame like Chrome.
|
// Create a bubble style frame like Chrome.
|
||||||
views::BubbleFrameView* frame = new views::BubbleFrameView(gfx::Insets());
|
views::BubbleFrameView* frame = new views::BubbleFrameView(gfx::Insets());
|
||||||
|
@ -390,28 +370,7 @@ void ShowMessageBox(NativeWindow* parent_window,
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShowErrorBox(const base::string16& title, const base::string16& content) {
|
void ShowErrorBox(const base::string16& title, const base::string16& content) {
|
||||||
#if defined(OS_WIN)
|
|
||||||
ui::MessageBox(NULL, content, title, MB_OK | MB_ICONERROR | MB_TASKMODAL);
|
ui::MessageBox(NULL, content, title, MB_OK | MB_ICONERROR | MB_TASKMODAL);
|
||||||
#elif defined(USE_X11)
|
|
||||||
if (Browser::Get()->is_ready()) {
|
|
||||||
GtkWidget* dialog = gtk_message_dialog_new(
|
|
||||||
NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
|
|
||||||
"%s", base::UTF16ToUTF8(title).c_str());
|
|
||||||
gtk_message_dialog_format_secondary_text(
|
|
||||||
GTK_MESSAGE_DIALOG(dialog),
|
|
||||||
"%s", base::UTF16ToUTF8(content).c_str());
|
|
||||||
gtk_dialog_run(GTK_DIALOG(dialog));
|
|
||||||
gtk_widget_destroy(dialog);
|
|
||||||
} else {
|
|
||||||
fprintf(stderr,
|
|
||||||
ANSI_TEXT_BOLD ANSI_BACKGROUND_GRAY
|
|
||||||
ANSI_FOREGROUND_RED "%s\n"
|
|
||||||
ANSI_FOREGROUND_BLACK "%s"
|
|
||||||
ANSI_RESET "\n",
|
|
||||||
base::UTF16ToUTF8(title).c_str(),
|
|
||||||
base::UTF16ToUTF8(content).c_str());
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace atom
|
} // namespace atom
|
|
@ -70,7 +70,7 @@ will be passed via `callback(filename)`
|
||||||
|
|
||||||
* `browserWindow` BrowserWindow
|
* `browserWindow` BrowserWindow
|
||||||
* `options` Object
|
* `options` Object
|
||||||
* `type` String - Can be `"none"`, `"info"` or `"warning"`
|
* `type` String - Can be `"none"`, `"info"`, `"error"`, `"question"` or `"warning"`
|
||||||
* `buttons` Array - Array of texts for buttons
|
* `buttons` Array - Array of texts for buttons
|
||||||
* `title` String - Title of the message box, some platforms will not show it
|
* `title` String - Title of the message box, some platforms will not show it
|
||||||
* `message` String - Content of the message box
|
* `message` String - Content of the message box
|
||||||
|
|
|
@ -175,8 +175,9 @@
|
||||||
'atom/browser/ui/file_dialog_mac.mm',
|
'atom/browser/ui/file_dialog_mac.mm',
|
||||||
'atom/browser/ui/file_dialog_win.cc',
|
'atom/browser/ui/file_dialog_win.cc',
|
||||||
'atom/browser/ui/message_box.h',
|
'atom/browser/ui/message_box.h',
|
||||||
|
'atom/browser/ui/message_box_gtk.cc',
|
||||||
'atom/browser/ui/message_box_mac.mm',
|
'atom/browser/ui/message_box_mac.mm',
|
||||||
'atom/browser/ui/message_box_views.cc',
|
'atom/browser/ui/message_box_win.cc',
|
||||||
'atom/browser/ui/tray_icon.cc',
|
'atom/browser/ui/tray_icon.cc',
|
||||||
'atom/browser/ui/tray_icon.h',
|
'atom/browser/ui/tray_icon.h',
|
||||||
'atom/browser/ui/tray_icon_gtk.cc',
|
'atom/browser/ui/tray_icon_gtk.cc',
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue