Skip to content

Commit

Permalink
Adds OSC 777 notification support.
Browse files Browse the repository at this point in the history
  • Loading branch information
christianparpart committed Jun 23, 2020
1 parent 175c8b1 commit 5935fa3
Show file tree
Hide file tree
Showing 18 changed files with 219 additions and 41 deletions.
5 changes: 3 additions & 2 deletions src/contour/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
include(FilesystemResolver)

find_package(Qt5 COMPONENTS Gui Network REQUIRED) # apt install qtbase5-dev libqt5gui5
find_package(Qt5 COMPONENTS Gui Network Widgets REQUIRED) # apt install qtbase5-dev libqt5gui5
find_package(OpenGL REQUIRED)
find_package(Freetype REQUIRED)

Expand All @@ -24,6 +24,7 @@ CIncludeMe(contour.yml "${CMAKE_CURRENT_BINARY_DIR}/contour_yaml.h" "default_con
set(contour_SRCS
Actions.cpp Actions.h
Config.cpp Config.h
Controller.cpp Controller.h
FileChangeWatcher.cpp FileChangeWatcher.h
LoggingSink.cpp LoggingSink.h
TerminalWindow.cpp TerminalWindow.h
Expand Down Expand Up @@ -74,7 +75,7 @@ elseif(APPLE)
)
endif()

set(contour_LIBRARIES terminal_view yaml-cpp Qt5::Gui Qt5::Network OpenGL::GL)
set(contour_LIBRARIES terminal_view yaml-cpp Qt5::Gui Qt5::Widgets Qt5::Network OpenGL::GL)
if(Boost_FILESYSTEM_FOUND)
target_include_directories(contour PRIVATE ${Boost_INCLUDE_DIRS})
list(APPEND contour_LIBRARIES ${Boost_LIBRARIES})
Expand Down
80 changes: 80 additions & 0 deletions src/contour/Controller.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/**
* This file is part of the "libterminal" project
* Copyright (c) 2019-2020 Christian Parpart <christian@parpart.family>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <contour/Controller.h>
#include <contour/TerminalWindow.h>

#include <QtCore/QProcess>

using namespace std;

namespace contour {

Controller::Controller(std::string _programPath,
config::Config _config,
std::string _profileName) :
programPath_{ move(_programPath) },
config_{ move(_config) },
profileName_{ move(_profileName) }
{
// systrayIcon_ = new QSystemTrayIcon(nullptr);
// systrayIcon_->show();
// TODO: systrayIcon_: add icon
// TODO: systrayIcon_: add context menu?

connect(this, &Controller::started, this, [this]() { newWindow(); });
}

Controller::~Controller()
{
}

void Controller::newWindow()
{
auto mainWindow = new TerminalWindow{
config_,
profileName_,
programPath_
};
mainWindow->show();

QObject::connect(mainWindow, &TerminalWindow::showNotification,
this, &Controller::showNotification);
}

void Controller::showNotification(QString const& _title, QString const& _content)
{
// systrayIcon_->showMessage(
// _title,
// _content,
// QSystemTrayIcon::MessageIcon::Information,
// 10 * 1000
// );

#if defined(__linux__)
// XXX requires notify-send to be installed.
QStringList args;
args.append("--urgency=low");
args.append("--expire-time=10000");
args.append("--category=terminal");
args.append(_title);
args.append(_content);
QProcess::execute(QString::fromLatin1("notify-send"), args);
#elif defined(__APPLE__)
// TODO: use Growl?
#elif defined(_WIN32)
// TODO: use Toast
#endif
}

} // end namespace
45 changes: 45 additions & 0 deletions src/contour/Controller.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* This file is part of the "libterminal" project
* Copyright (c) 2019-2020 Christian Parpart <christian@parpart.family>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once

#include <contour/Config.h>

#include <QtCore/QThread>
#include <QtWidgets/QSystemTrayIcon>

#include <string>

namespace contour {

class Controller : public QThread {
public:
Controller(std::string _programPath,
contour::config::Config _config,
std::string _profileName);

~Controller();

public slots:
void newWindow();
void showNotification(QString const& _title, QString const& _content);

private:
std::string programPath_;
contour::config::Config config_;
std::string profileName_;

QSystemTrayIcon* systrayIcon_ = nullptr;
};

} // end namespace
29 changes: 18 additions & 11 deletions src/contour/TerminalWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,18 @@
#include <contour/Actions.h>
#include <terminal/Metrics.h>

#include <QClipboard>
#include <QDebug>
#include <QDesktopServices>
#include <QGuiApplication>
#include <QKeyEvent>
#include <QOpenGLFunctions>
#include <QOpenGLWindow>
#include <QProcess>
#include <QScreen>
#include <QTimer>

#include <QtCore/QDebug>
#include <QtCore/QFileInfo>
#include <QtCore/QProcess>
#include <QtCore/QTimer>
#include <QtGui/QClipboard>
#include <QtGui/QDesktopServices>
#include <QtGui/QKeyEvent>
#include <QtGui/QOpenGLFunctions>
#include <QtGui/QOpenGLWindow>
#include <QtGui/QScreen>
#include <QtNetwork/QHostInfo>
#include <QtWidgets/QApplication>

#if defined(CONTOUR_BLUR_PLATFORM_KWIN)
#include <KWindowEffects>
Expand Down Expand Up @@ -512,6 +511,7 @@ void TerminalWindow::initializeGL()
ortho(0.0f, static_cast<float>(width()), 0.0f, static_cast<float>(height())),
bind(&TerminalWindow::onScreenUpdate, this, _1),
bind(&TerminalWindow::onWindowTitleChanged, this),
bind(&TerminalWindow::onNotify, this, _1, _2),
bind(&TerminalWindow::onDoResize, this, _1, _2, _3),
bind(&TerminalWindow::onTerminalClosed, this),
*config::Config::loadShaderConfig(config::ShaderClass::Background),
Expand Down Expand Up @@ -1181,6 +1181,7 @@ void TerminalWindow::onBell()
{
if (logger_.sink())
*logger_.sink() << "TODO: Beep!\n";
QApplication::beep();
// QApplication::beep() requires Qt Widgets dependency. doesn't suound good.
// so maybe just a visual bell then? That would require additional OpenGL/shader work then though.
}
Expand Down Expand Up @@ -1226,6 +1227,12 @@ void TerminalWindow::onWindowTitleChanged()
});
}

void TerminalWindow::onNotify(string const& _title, string const& _content)
{
emit showNotification(QString::fromUtf8(_title.data(), _title.size()),
QString::fromUtf8(_content.data(), _content.size()));
}

void TerminalWindow::onDoResize(unsigned _width, unsigned _height, bool _inPixels)
{
bool resizePending = false;
Expand Down
5 changes: 5 additions & 0 deletions src/contour/TerminalWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include <QOpenGLWindow>
#include <QOpenGLExtraFunctions>
#include <QTimer>
#include <QtWidgets/QSystemTrayIcon>

#include <atomic>
#include <fstream>
Expand Down Expand Up @@ -100,6 +101,7 @@ class TerminalWindow :

void onScreenUpdate(std::vector<terminal::Command> const& _commands);
void onWindowTitleChanged();
void onNotify(std::string const& _title, std::string const& _content);
void onDoResize(unsigned _width, unsigned _height, bool _inPixels);
void onConfigReload(FileChangeWatcher::Event /*_event*/);
void onTerminalClosed();
Expand All @@ -108,6 +110,9 @@ class TerminalWindow :

void setDefaultCursor();

signals:
void showNotification(QString const& _title, QString const& _body);

private:
/// Declares the screen-dirtiness-vs-rendering state.
enum class State {
Expand Down
33 changes: 17 additions & 16 deletions src/contour/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@
* limitations under the License.
*/
#include <contour/Config.h>
#include <contour/TerminalWindow.h>
#include <contour/Controller.h>

#include <terminal/ParserTables.h>

#include <QCommandLineParser>
#include <QGuiApplication>
#include <QtCore/QCommandLineParser>
#include <QtCore/QThread>
#include <QtWidgets/QApplication>

#include <iostream>

Expand Down Expand Up @@ -61,13 +63,13 @@ int main(int argc, char* argv[])
{
try
{
QGuiApplication::setApplicationName("contour");
QGuiApplication::setOrganizationName("contour");
QGuiApplication::setApplicationVersion(QString::fromStdString(fmt::format(
QCoreApplication::setApplicationName("contour");
QCoreApplication::setOrganizationName("contour");
QCoreApplication::setApplicationVersion(QString::fromStdString(fmt::format(
"{}.{}.{}", CONTOUR_VERSION_MAJOR, CONTOUR_VERSION_MINOR, CONTOUR_VERSION_PATCH
)));
QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication app(argc, argv);

auto cli = contour::CLI{};
cli.process(app);
Expand Down Expand Up @@ -131,16 +133,15 @@ int main(int argc, char* argv[])
shell.arguments.push_back(positionalArgs.at(i).toStdString());
}

//QSurfaceFormat::setDefaultFormat(contour::TerminalWindow::surfaceFormat());
contour::Controller controller(argv[0], config, profileName);
controller.start();

auto mainWindow = contour::TerminalWindow{
config,
profileName,
argv[0]
};
mainWindow.show();
auto const rv = app.exec();

controller.exit();
controller.wait();

return app.exec();
return rv;
}
catch (exception const& e)
{
Expand Down
1 change: 1 addition & 0 deletions src/terminal/Commands.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ class MnemonicBuilder {
void operator()(HorizontalTabSet) { build("HTS", "Horizontal Tab Set"); }
void operator()(Hyperlink const& v) { build("HYPERLINK", fmt::format("Set hyperlink id='{}' link='{}'", v.id, v.link)); }
void operator()(MoveCursorUp const& v) { build("CUU", "Move cursor up", v.n); }
void operator()(Notify const&) { build("NOTIFY", "Send user notification"); }
void operator()(MoveCursorDown const& v) { build("CUD", "Move cursor down", v.n); }
void operator()(MoveCursorForward const& v) { build("CUF", "Move cursor forward", v.n); }
void operator()(MoveCursorBackward const& v) { build("CUB", "Move cursor backward", v.n); }
Expand Down
7 changes: 7 additions & 0 deletions src/terminal/Commands.h
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,12 @@ struct Hyperlink {
std::string link;
};

/// OSC 777 - notify
struct Notify {
std::string title;
std::string content;
};

// {{{ config commands
/// OSC color-setting related commands that can be grouped into one
enum class DynamicColorName {
Expand Down Expand Up @@ -936,6 +942,7 @@ using Command = std::variant<
MoveCursorToLine,
MoveCursorToNextTab,
MoveCursorUp,
Notify,
ReportCursorPosition,
ReportExtendedCursorPosition,
RequestDynamicColor,
Expand Down
1 change: 1 addition & 0 deletions src/terminal/OutputGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ void OutputGenerator::operator()(Command const& command)
[&](MoveCursorTo const& to) { write("\033[{}H", pairOrNone(1, to.row, to.column)); },
[&](MoveCursorToLine const& to) { write("\033[{}d", to.row); },
[&](MoveCursorToNextTab) { write("\t"); },
[&](Notify const& v) { write("\033]777;notify;{};{}\033\\", v.title, v.content); },
[&](CursorBackwardTab const& v) { write("\033[{}Z", v.count); },
[&](SaveCursor) { write("\0337"); },
[&](RestoreCursor) { write("\0338"); },
Expand Down
7 changes: 7 additions & 0 deletions src/terminal/OutputHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,13 @@ void OutputHandler::dispatchOSC()
case -'L': // Ps = L ; c -> Set icon label.
log<UnsupportedOutputEvent>("OSC " + intermediateCharacters_);
break;
case 777:
{
auto const splits = crispy::split(value, ';');
if (splits.size() == 3 && splits[0] == "notify")
emitCommand<Notify>(string(splits[1]), string(splits[2]));
}
break;
default:
log<InvalidOutputEvent>("OSC " + intermediateCharacters_, "Unknown");
break;
Expand Down
13 changes: 11 additions & 2 deletions src/terminal/Screen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -709,7 +709,8 @@ Screen::Screen(WindowSize const& _size,
std::function<RGBColor(DynamicColorName)> _requestDynamicColor,
std::function<void(DynamicColorName)> _resetDynamicColor,
std::function<void(DynamicColorName, RGBColor const&)> _setDynamicColor,
std::function<void(bool)> _setGenerateFocusEvents
std::function<void(bool)> _setGenerateFocusEvents,
NotifyCallback _notify
) :
onCommands_{ move(onCommands) },
logger_{ _logger },
Expand Down Expand Up @@ -740,7 +741,8 @@ Screen::Screen(WindowSize const& _size,
requestDynamicColor_{ move(_requestDynamicColor) },
resetDynamicColor_{ move(_resetDynamicColor) },
setDynamicColor_{ move(_setDynamicColor) },
setGenerateFocusEvents_{ move(_setGenerateFocusEvents) }
setGenerateFocusEvents_{ move(_setGenerateFocusEvents) },
notify_{ move(_notify) }
{
(*this)(SetMode{Mode::AutoWrap, true});
}
Expand Down Expand Up @@ -1393,6 +1395,13 @@ void Screen::operator()(MoveCursorToNextTab const&)
}
}

void Screen::operator()(Notify const& _notify)
{
cout << "Screen.NOTIFY: title: '" << _notify.title << "', content: '" << _notify.content << "'\n";
if (notify_)
notify_(_notify.title, _notify.content);
}

void Screen::appendSpaceChars(size_t n)
{
// Don't do (*this)(MoveCursorForward{n}) because we actually want spaces.
Expand Down

0 comments on commit 5935fa3

Please sign in to comment.