16 Commits
1.5 ... 1.7

Author SHA1 Message Date
KingToolbox
150635107e [BugFix] WinPty can not launch applications with spaces in the path. 2020-09-19 01:07:12 +08:00
KingToolbox
8a0d506c52 Remove the QuickBar screenshot 2020-09-19 01:01:42 +08:00
kingToolbox
fc213aa6fe Update features and roadmap of version 1.7 2020-09-19 00:59:23 +08:00
KingToolbox
86359ef6a4 Add a screenshot of dige-white theme 2020-09-19 00:41:19 +08:00
kingToolbox
589a28a22d Add a qualifier again 2020-08-28 16:18:21 +08:00
kingToolbox
a5ab332b20 Add a qualifier 2020-08-28 15:13:25 +08:00
kingToolbox
fdc12226d7 Add open source statement 2020-08-28 15:10:39 +08:00
KingToolbox
8f73b75e79 A popup widget. 2020-08-28 15:06:48 +08:00
KingToolbox
2e76fa927c A scrollbar supports 64-bit ranges. 2020-08-28 15:03:36 +08:00
KingToolbox
78e57fb850 Rearrange some classes 2020-08-28 14:56:20 +08:00
KingToolbox
cf08fe14ca Merge branch 'master' of https://github.com/kingToolbox/WindTerm 2020-08-28 14:48:56 +08:00
KingToolbox
685cf3111d Add a high-performance thread local storage class. 2020-08-28 14:47:23 +08:00
kingToolbox
d1eea0c9ef Fix a typo 2020-08-28 13:29:20 +08:00
kingToolbox
f5232e9226 Add a detailed description about open source. 2020-08-28 13:21:43 +08:00
KingToolbox
d5c33239e5 Add an implementation of Telnet protocol 2020-08-24 15:11:57 +08:00
KingToolbox
23570ead27 Add WinPty and ConPty 2020-08-24 14:47:21 +08:00
25 changed files with 1898 additions and 21 deletions

View File

@@ -7,7 +7,7 @@ _Hello WindTerm :rose:, hello world!_
# License
**Completely FREE for commercial and non-commercial use without limitations.**
**All source codes (except thirdparty directory) are provided under the terms of Apache-2.0 license.**
**All released source codes (except thirdparty directory) are provided under the terms of Apache-2.0 license.**
# Introduction
@@ -19,6 +19,12 @@ Windows Binary: https://github.com/kingToolbox/WindTerm/releases
(The binary of Macos and Linux will be committed later)
# Source Code
WindTerm is a **partial** open source project, and the source will be gradually opened.
Open source code includes, but is not limited to, the classes that can be used independently, such as functional, algorithms, gui widgets, etc., as well as functional libraries, such as network, protocols, etc., as well as all types that require open source according to the license.
# Issues and feature requests
Any issues and feature requests are welcome. Please click [issues](https://github.com/kingToolbox/WindTerm/issues) to commit an issue.
@@ -29,17 +35,22 @@ Main Window:
![MainWindow](https://github.com/kingToolbox/WindTerm/blob/master/images/screenshots/WindTerm.png)
Quick Bar:
![QuickBar](https://github.com/kingToolbox/WindTerm/blob/master/images/screenshots/QuickBar.png)
Split views:
![SplitView](https://github.com/kingToolbox/WindTerm/blob/master/images/screenshots/SplitView.png)
DigeWhite Theme:
![DigeWhite Theme](https://github.com/kingToolbox/WindTerm/blob/master/images/screenshots/WindTerm_DigeWhite_Theme.png)
# Features
- **Supports changing the UI theme.** [Intro Video](https://kingtoolbox.github.io/2020/09/18/theme/)
- **Supports setting the tab color.** [Intro Video](https://kingtoolbox.github.io/2020/09/18/tabbar-change-tabcolor/)
- **Supports closing tabs to the right.**
- **Supports highlighting the opening and closing delimiter, such as (), [], {} and the customed delimiters.** [Intro Video](https://kingtoolbox.github.io/2020/06/28/pair/)
- **Integrated sftp, scp client, supports download, upload, remove, rename, make new file/directory and so on.** [Intro Video](https://kingtoolbox.github.io/tags/transfer/)
- **Integrated local file manager, supports move to, copy to, copy from, remove, rename, make new file/directory and so on.**
- **Local and remote modes with vim keybindings. (Using Shift+Enter key to switch between remote and local mode**) [Intro Video](https://kingtoolbox.github.io/2020/06/21/keyboard-modes/)
- **Command palette.** [Intro Video](https://kingtoolbox.github.io/tags/command-palette/)
- **Command sender.** [Intro Video](https://kingtoolbox.github.io/tags/sender/)
@@ -271,8 +282,9 @@ DIGEdit is the text component of WindTerm.
2-4 weeks.
**Next release (Early Septemberfor reference only):**
- Auto complete
**Next release (Early Octoberfor reference only):**
- **Linux Version**
- Linux bash
- External tools
**Todo list:**
@@ -282,11 +294,10 @@ DIGEdit is the text component of WindTerm.
- ProxyCommand
- UI:
- Config dialog
- Local filer for cmd, powershell
- Terminal:
- Linux bash
- MacOs bash
- Session:
- Auto Complete
- Proxy
- Chat mode
- Log viewer

Binary file not shown.

Before

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 KiB

36
src/Protocols/Protocol.h Normal file
View File

@@ -0,0 +1,36 @@
/*
* Copyright 2020, WindTerm.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
#ifndef PROTOCOL_H
#define PROTOCOL_H
#pragma once
#include <QByteArray>
class Protocol
{
public:
Protocol(void) = default;
virtual ~Protocol(void) = default;
QByteArray name() const { return m_name; }
protected:
QByteArray m_name;
};
#endif // PROTOCOL_H

View File

@@ -0,0 +1,93 @@
/*
* Copyright 2020, WindTerm.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 "TelnetProtocol.h"
#include <iomanip>
#include <mutex>
#include <sstream>
const char *arrTelnetCmds[] =
{
"XEOF", "SUSP", "ABORT", "EOR", "SE",
"NOP", "DM", "BREAK", "IP", "AO",
"AYT", "EC", "EL", "GA", "SB",
"WILL", "WONT", "DO", "DONT", "IAC"
};
const char *arrTelnetOpts[] =
{
"BINARY", "ECHO", "RCP", "SUPPRESS GO AHEAD", "NAME", "STATUS", "TIMING MARK", "RCTE",
"NAOL", "NAOP", "NAOCRD", "NAOHTS", "NAOHTD", "NAOFFD", "NAOVTS", "NAOVTD",
"NAOLFD", "EXTEND ASCII", "LOGOUT", "BYTE MACRO", "DATA ENTRY TERMINAL", "SUPDUP", "SUPDUP OUTPUT", "SEND LOCATION",
"TERMINAL TYPE", "END OF RECORD", "TACACS UID", "OUTPUT MARKING", "TTYLOC", "3270 REGIME", "X.3 PAD", "NAWS",
"TSPEED", "LFLOW", "LINEMODE", "XDISPLOC", "OLD ENVIRON", "AUTHENTICATION", "ENCRYPT", "NEW ENVIRON",
"TN3270E", "XAUTH", "CHARSET", "RSP", "COM PORT CONTROL", "SUPPRESS LOCAL ECHO", "START TLS", "KERMIT",
"SEND-URL", "FORWARD_X",
};
std::string TelnetProtocol::GetCommandName(uchar chTelnetCmd)
{
if (chTelnetCmd >= TELCMD_XEOF && chTelnetCmd <= TELCMD_IAC)
return arrTelnetCmds[chTelnetCmd - TELCMD_XEOF];
std::ostringstream oss;
oss << "0x" << std::hex << std::setw(2) << std::setfill('0') << (int)chTelnetCmd << "(Unknown Telnet Command)";
return oss.str();
}
std::string TelnetProtocol::GetOptionName(uchar chTelnetOption)
{
if (chTelnetOption >= TELOPT_BINARY && chTelnetOption <= TELOPT_FORWARD_X) { return arrTelnetOpts[chTelnetOption - TELOPT_BINARY];}
else if (chTelnetOption == TELOPT_MCCP1) { return "MUD COMPRESSION PROTOCOL (V1)"; }
else if (chTelnetOption == TELOPT_MCCP2) { return "MUD COMPRESSION PROTOCOL (V2)"; }
else if (chTelnetOption == TELOPT_MSP) { return "MUD SOUND PROTOCOL"; }
else if (chTelnetOption == TELOPT_MXP) { return "MUD EXTENSION PROTOCOL";}
else if (chTelnetOption == TELOPT_PRAGMA_LOGON) { return "TELOPT PRAGMA LOGON"; }
else if (chTelnetOption == TELOPT_SSPI_LOGON) { return "TELOPT SSPI LOGON"; }
else if (chTelnetOption == TELOPT_PRAGMA_HEARTBEAT) { return "TELOPT PRAGMA HEARTBEAT"; }
else if (chTelnetOption == TELOPT_EXOPL) { return "EXTENDED OPTIONS LIST"; }
std::ostringstream oss;
oss << "0x" << std::hex << std::setw(2) << std::setfill('0') << (int)chTelnetOption << "(Unknown Telnet Option)";
return oss.str();
}
TelnetOptionVector &TelnetProtocol::GetOptionVector()
{
static TelnetOptionVector m_vTelnetOption;
static std::once_flag flag;
std::call_once(flag, [&]() {
m_vTelnetOption.clear();
m_vTelnetOption.resize(TELOPT_INDEX_MAX);
m_vTelnetOption[TELOPT_INDEX_NAWS].Init(TELOPT_NAWS, TELNET_CLIENT, TELOPT_STATE_INACTIVE_REQUESTED);
m_vTelnetOption[TELOPT_INDEX_TTYPE].Init(TELOPT_TTYPE, TELNET_CLIENT, TELOPT_STATE_INACTIVE_REQUESTED);
m_vTelnetOption[TELOPT_INDEX_ECHO].Init(TELOPT_ECHO, TELNET_SERVER, TELOPT_STATE_INACTIVE_REQUESTED);
m_vTelnetOption[TELOPT_INDEX_SERVER_SGA].Init(TELOPT_SGA, TELNET_SERVER, TELOPT_STATE_INACTIVE_REQUESTED);
m_vTelnetOption[TELOPT_INDEX_CLIENT_SGA].Init(TELOPT_SGA, TELNET_CLIENT, TELOPT_STATE_INACTIVE_REQUESTED);
m_vTelnetOption[TELOPT_INDEX_SERVER_BIN].Init(TELOPT_BINARY, TELNET_SERVER, TELOPT_STATE_INACTIVE);
m_vTelnetOption[TELOPT_INDEX_CLIENT_BIN].Init(TELOPT_BINARY, TELNET_CLIENT, TELOPT_STATE_INACTIVE);
});
return m_vTelnetOption;
}
TelnetProtocol::TelnetProtocol()
{
m_name = "Telnet";
}

View File

@@ -0,0 +1,206 @@
/*
* Copyright 2020, WindTerm.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
#ifndef TELNETPROTOCOL_H
#define TELNETPROTOCOL_H
#pragma once
#include <vector>
#include "Protocol.h"
typedef unsigned char uchar;
/* TELNET Command Codes: */
constexpr uchar TELCMD_XEOF = 236; /* End of file: EOF is already used */
constexpr uchar TELCMD_SUSP = 237; /* Suspend process */
constexpr uchar TELCMD_ABORT = 238; /* Abort process */
constexpr uchar TELCMD_EOR = 239; /* end of record (transparent mode) */
constexpr uchar TELCMD_SE = 240; /* end sub negotiation */
constexpr uchar TELCMD_NOP = 241; /* nop */
constexpr uchar TELCMD_DM = 242; /* data mark--for connect. cleaning */
constexpr uchar TELCMD_BREAK = 243; /* break */
constexpr uchar TELCMD_IP = 244; /* interrupt process--permanently */
constexpr uchar TELCMD_AO = 245; /* abort output--but let prog finish*/
constexpr uchar TELCMD_AYT = 246; /* are you there */
constexpr uchar TELCMD_EC = 247; /* erase the current character */
constexpr uchar TELCMD_EL = 248; /* erase the current line */
constexpr uchar TELCMD_GA = 249; /* you may reverse the line */
constexpr uchar TELCMD_SB = 250; /* interpret as subnegotiation */
constexpr uchar TELCMD_WILL = 251; /* I will use option */
constexpr uchar TELCMD_WONT = 252; /* I won't use option */
constexpr uchar TELCMD_DO = 253; /* please, you use option */
constexpr uchar TELCMD_DONT = 254; /* you are not to use option */
constexpr uchar TELCMD_IAC = 255; /* interpret as command */
/* The telnet options represented as strings */
constexpr uchar TELOPT_BINARY = 0; // Binary Transmission - RFC 856
constexpr uchar TELOPT_ECHO = 1; // Echo - RFC 857
constexpr uchar TELOPT_RCP = 2; // Reconnection
constexpr uchar TELOPT_SGA = 3; // Suppress Go Ahead - RFC 858
constexpr uchar TELOPT_NAMS = 4; // Approx Message Size Negotiation
constexpr uchar TELOPT_STATUS = 5; // Status - RFC 859
constexpr uchar TELOPT_TM = 6; // Timing Mark - RFC 860
constexpr uchar TELOPT_RCTE = 7; // Remote controlled transmission and echo - RFC 563,726
constexpr uchar TELOPT_NAOL = 8; // Negotiate about output line width - NIC50005
constexpr uchar TELOPT_NAOP = 9; // Negotiate about output page size - NIC50005
constexpr uchar TELOPT_NAOCRD = 10; // Negotiate about CR disposition - RFC 652
constexpr uchar TELOPT_NAOHTS = 11; // Negotiate about horizontal tabstops - RFC 653
constexpr uchar TELOPT_NAOHTD = 12; // Negotiate about horizontal tab disposition - RFC 654
constexpr uchar TELOPT_NAOFFD = 13; // Negotiate about formfeed disposition - RFC 655
constexpr uchar TELOPT_NAOVTS = 14; // Negotiate about vertical tab stops - RFC 656
constexpr uchar TELOPT_NAOVTD = 15; // Negotiate about vertical tab disposition - RFC 657
constexpr uchar TELOPT_NAOLFD = 16; // Negotiate about output LF disposition - RFC 658
constexpr uchar TELOPT_XASCII = 17; // Extended ascic character set - RFC 698
constexpr uchar TELOPT_LOGOUT = 18; // Force logout - RFC 727
constexpr uchar TELOPT_BM = 19; // Byte Macro - RFC 735
constexpr uchar TELOPT_DET = 20; // Data Entry Terminal - RFC 732,1043
constexpr uchar TELOPT_SUPDUP = 21; // SUPDUP Protocol - RFC 734,736
constexpr uchar TELOPT_SUPDUPOUTPUT = 22; // SUPDUP Output - RFC 749
constexpr uchar TELOPT_SNDLOC = 23; // Send Location - RFC 779
constexpr uchar TELOPT_TTYPE = 24; // Terminal Type - RFC 1091
constexpr uchar TELOPT_EOR = 25; // End of Record - RFC 885
constexpr uchar TELOPT_TUID = 26; // TACACS User Identification - RFC 927
constexpr uchar TELOPT_OUTMRK = 27; // Output Marking - RFC 933
constexpr uchar TELOPT_TTYLOC = 28; // Terminal Location Number - RFC 946
constexpr uchar TELOPT_3270REGIME = 29; // Telnet 3270 Regime - RFC 1041
constexpr uchar TELOPT_X3PAD = 30; // X.3 PAD - RFC 1053
constexpr uchar TELOPT_NAWS = 31; // Negotiate window size - RFC 1073
constexpr uchar TELOPT_TSPEED = 32; // Terminal Speed - RFC 1079
constexpr uchar TELOPT_LFLOW = 33; // Remote Flow Control - RFC 1372
constexpr uchar TELOPT_LINEMODE = 34; // Linemode option - RFC 1184
constexpr uchar TELOPT_XDISPLOC = 35; // X Display Location - RFC 1096
constexpr uchar TELOPT_OLD_ENVIRON = 36; // Environment Option - RFC 1408
constexpr uchar TELOPT_AUTHENTICATION = 37; // Authenticate - RFC 1416,2941,2942,2943,2951
constexpr uchar TELOPT_ENCRYPT = 38; // Encryption Option - RFC 2946
constexpr uchar TELOPT_NEW_ENVIRON = 39; // New Environment Option - RFC 1572
constexpr uchar TELOPT_TN3270E = 40; // TN3270 enhancements - RFC 2355
constexpr uchar TELOPT_XAUTH = 41; // XAUTH
constexpr uchar TELOPT_CHARSET = 42; // Negotiate charset to use - RFC 2066
constexpr uchar TELOPT_RSP = 43; // Telnet remote serial port
constexpr uchar TELOPT_COM_PORT_OPTION = 44; // Com port control option - RFC 2217
constexpr uchar TELOPT_SLE = 45; // Telnet suppress local echo
constexpr uchar TELOPT_STARTTLS = 46; // Telnet Start TLS
constexpr uchar TELOPT_KERMIT = 47; // Automatic Kermit file transfer - RFC 2840
constexpr uchar TELOPT_SEND_URL = 48; // Send URL
constexpr uchar TELOPT_FORWARD_X = 49; // X forwarding
constexpr uchar TELOPT_MCCP1 = 85; // Mud Compression Protocol (v1)
constexpr uchar TELOPT_MCCP2 = 86; // Mud Compression Protocol (v2)
constexpr uchar TELOPT_MSP = 90; // Mud Sound Protocol
constexpr uchar TELOPT_MXP = 91; // Mud eXtension Protocol
constexpr uchar TELOPT_ZMP = 93; // Zenith Mud Protocol
constexpr uchar TELOPT_PRAGMA_LOGON = 138; // Telnet option pragma logon
constexpr uchar TELOPT_SSPI_LOGON = 139; // Telnet option SSPI login
constexpr uchar TELOPT_PRAGMA_HEARTBEAT = 140; // Telnet option pragma heartbeat
constexpr uchar TELOPT_GMCP = 201; // Generic Mud Communication Protocol
constexpr uchar TELOPT_EXOPL = 255; // extended-options-list - RFC 861
/* Option Subnegotiation Constants: */
constexpr uchar TELSUB_IS = 0; // An option IS
constexpr uchar TELSUB_SEND = 1; // Send an option
constexpr uchar TELSUB_INFO = 2; // Environ: informational version of IS
constexpr uchar TELSUB_NAME = 3;
/* Keyboard Command Characters: */
constexpr char TELKEY_LF = '\n'; // Line Feed
constexpr char TELKEY_CR = '\r'; // Carriage Return
constexpr char TELKEY_BEL = '\a'; // Bell (attention signal)
constexpr char TELKEY_BS = '\b'; // Back Space
constexpr char TELKEY_HT = '\t'; // Horizontal Tab
constexpr char TELKEY_VT = '\v'; // Vertical Tab
constexpr char TELKEY_FF = '\f'; // Form Feed
enum TelnetOptionIndex : uchar {
TELOPT_INDEX_NAWS,
TELOPT_INDEX_TSPEED,
TELOPT_INDEX_TTYPE,
TELOPT_INDEX_NEW_ENVIRON,
TELOPT_INDEX_LFLOW,
TELOPT_INDEX_LINEMODE,
TELOPT_INDEX_ECHO,
TELOPT_INDEX_SERVER_SGA,
TELOPT_INDEX_CLIENT_SGA,
TELOPT_INDEX_SERVER_BIN,
TELOPT_INDEX_CLIENT_BIN,
TELOPT_INDEX_MAX
};
enum TelnetAction : uchar {
TELACT_ABORT, // Abort Process
TELACT_AO, // Abort Output
TELACT_AYT, // Are You There
TELACT_BREAK, // serial-line break
TELACT_EC, // Erase Character
TELACT_EL, // Erase Line
TELACT_EOF, // end-of-file on session input
TELACT_EOL, // Telnet end-of-line sequence (CRLF, as opposed to CR NUL that escapes a literal CR)
TELACT_EOR, // End Of Record
TELACT_GA, // Go Ahead
TELACT_IP, // Interrupt Process
TELACT_NOP, // transmit data with no effect
TELACT_SUSP // Suspend Process
};
enum TelnetSide : uchar {
TELNET_SERVER,
TELNET_CLIENT
};
#define BIT_INACTIVE 0x00
#define BIT_ACTIVE 0x01
#define BIT_REQUESTED 0x10
enum TelnetOptionState {
TELOPT_STATE_INACTIVE = BIT_INACTIVE,
TELOPT_STATE_ACTIVE = BIT_ACTIVE,
TELOPT_STATE_INACTIVE_REQUESTED = BIT_INACTIVE | BIT_REQUESTED,
TELOPT_STATE_ACTIVE_REQUESTED = BIT_ACTIVE | BIT_REQUESTED
};
struct TelnetOption {
int nTelnetOption;
TelnetSide eTelnetSide;
TelnetOptionState eOptionState;
TelnetOption() :
nTelnetOption(-1),
eTelnetSide(TELNET_SERVER),
eOptionState(TELOPT_STATE_INACTIVE)
{}
void Init(uchar _nTelnetOption, TelnetSide _eTelnetSide, TelnetOptionState _eOptionState)
{
nTelnetOption = _nTelnetOption;
eTelnetSide = _eTelnetSide;
eOptionState = _eOptionState;
}
};
typedef std::vector<TelnetOption> TelnetOptionVector;
typedef std::vector<TelnetOptionState> TelnetOptionStateVector;
class TelnetProtocol : public Protocol
{
public:
TelnetProtocol();
std::string GetCommandName(uchar chTelnetCmd);
std::string GetOptionName(uchar chTelnetOpt);
TelnetOptionVector &GetOptionVector();
};
#endif // TELNETPROTOCOL_H

261
src/Pty/ConPty.cpp Normal file
View File

@@ -0,0 +1,261 @@
/*
* Copyright 2020, WindTerm.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 "ConPty.h"
#include <QThread>
#ifdef Q_OS_WIN
#include <process.h>
#endif
#define CONPTY_MINIMAL_WINDOWS_VERSION 18309
class PipeThread : public QThread {
public:
PipeThread(ConPty *conpty, LPVOID pipe)
: QThread(conpty)
, m_conpty(conpty)
, m_pipe(pipe)
{}
void run() final {
constexpr DWORD BUFF_SIZE = 512;
char szBuffer[BUFF_SIZE];
while (isInterruptionRequested() == false) {
if (isInterruptionRequested()) {
return;
}
DWORD bytesRead;
bool readSuccess = ReadFile(m_pipe, szBuffer, BUFF_SIZE, &bytesRead, NULL);
if (readSuccess == false) {
m_conpty->setErrorCode(GetLastError());
return;
}
if (isInterruptionRequested()) {
return;
}
if (readSuccess && bytesRead > 0) {
m_conpty->appendBuffer(QByteArray(szBuffer, bytesRead));
}
}
}
private:
ConPty *m_conpty;
LPVOID m_pipe;
};
ConPty::ConPty(QObject *parent /*= nullptr*/)
: m_inPipe(INVALID_HANDLE_VALUE)
, m_outPipe(INVALID_HANDLE_VALUE)
, m_pipeThread(nullptr)
, m_ptyHandler(INVALID_HANDLE_VALUE)
{}
ConPty::~ConPty() {
stop();
}
void ConPty::appendBuffer(const QByteArray &buffer) {
if (buffer.isEmpty() == false) {
{
ThreadLocker<SpinMutex> locker(m_mutex);
m_buffer.append(buffer);
}
emit readyRead();
}
}
HRESULT ConPty::createPseudoConsoleAndPipes(HPCON *phPC, HANDLE *phPipeIn, HANDLE *phPipeOut,
qint16 rows, qint16 columns) {
HRESULT hr = E_UNEXPECTED;
HANDLE hPipePTYIn = INVALID_HANDLE_VALUE;
HANDLE hPipePTYOut = INVALID_HANDLE_VALUE;
if (CreatePipe(&hPipePTYIn, phPipeOut, NULL, 0) && CreatePipe(phPipeIn, &hPipePTYOut, NULL, 0)) {
#if COMPILE_CONPTY_ENABLED
hr = CreatePseudoConsole({columns, rows}, hPipePTYIn, hPipePTYOut, 0, phPC);
#endif
if (INVALID_HANDLE_VALUE != hPipePTYOut) CloseHandle(hPipePTYOut);
if (INVALID_HANDLE_VALUE != hPipePTYIn) CloseHandle(hPipePTYIn);
}
return hr;
}
HRESULT ConPty::initStartupInfoAttachedToPseudoConsole(STARTUPINFOEX *pStartupInfo, HPCON hPC) {
HRESULT hr = E_UNEXPECTED;
if (pStartupInfo) {
SIZE_T attrListSize;
pStartupInfo->StartupInfo.cb = sizeof(STARTUPINFOEX);
InitializeProcThreadAttributeList(NULL, 1, 0, &attrListSize);
pStartupInfo->lpAttributeList = reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(malloc(attrListSize));
if (pStartupInfo->lpAttributeList
&& InitializeProcThreadAttributeList(pStartupInfo->lpAttributeList, 1, 0, &attrListSize)) {
hr = UpdateProcThreadAttribute(
pStartupInfo->lpAttributeList,
0,
PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
hPC,
sizeof(HPCON),
NULL,
NULL
) ? S_OK : HRESULT_FROM_WIN32(GetLastError());
} else {
hr = HRESULT_FROM_WIN32(GetLastError());
}
}
return hr;
}
bool ConPty::createProcess(QString command, const QString &arguments,
const QString &workingDirectory, const QStringList &environment,
qint16 rows, qint16 columns) {
if (isAvailable() == false) {
setErrorString(tr("Windows 10 version below 1809 is not supported."));
return false;
}
stop();
HRESULT hr = createPseudoConsoleAndPipes(&m_ptyHandler, &m_inPipe, &m_outPipe, rows, columns);
if (hr == S_OK) {
m_startupInfo = std::make_unique<STARTUPINFOEX>();
m_processInformation = std::make_unique<PROCESS_INFORMATION>();
m_pipeThread = new PipeThread(this, m_inPipe);
m_pipeThread->start();
if (initStartupInfoAttachedToPseudoConsole(m_startupInfo.get(), m_ptyHandler) == S_OK) {
std::wstring env = environment.join(QChar('\0')).append(QChar('\0')).toStdWString();
if (arguments.isEmpty() == false) {
command.append(" ").append(arguments);
}
LPWSTR szCommand = new wchar_t[command.size() + 1];
int commandLength = command.toWCharArray(szCommand);
szCommand[commandLength] = '\0';
hr = CreateProcess(
NULL,
szCommand,
NULL,
NULL,
FALSE,
EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT,
LPWSTR(env.data()),
workingDirectory.isEmpty() ? NULL : workingDirectory.toStdWString().c_str(),
&m_startupInfo->StartupInfo,
m_processInformation.get()
) ? S_OK : GetLastError();
delete szCommand;
szCommand = nullptr;
}
}
if (hr == S_OK) {
m_rows = rows;
m_columns = columns;
installWinProcessEventNotifier(m_processInformation->hProcess);
} else {
setErrorCode(GetLastError());
}
return true;
}
bool ConPty::isAvailable() {
qint32 buildNumber = QSysInfo::kernelVersion().split(".").last().toInt();
return (buildNumber >= CONPTY_MINIMAL_WINDOWS_VERSION) ? true : false;
}
QByteArray ConPty::readAll() {
ThreadLocker<SpinMutex> locker(m_mutex);
return std::move(m_buffer);
}
bool ConPty::resizeWindow(qint16 rows, qint16 columns) {
bool success = true;
if (rows != m_rows && columns != m_columns) {
#if COMPILE_CONPTY_ENABLED
HRESULT hr = (m_ptyHandler != INVALID_HANDLE_VALUE)
? ResizePseudoConsole(m_ptyHandler, { columns, rows })
: S_FALSE;
success = (hr == S_OK) ? true : false;
#endif
if (success) {
rows = m_rows;
columns = m_columns;
}
}
Q_ASSERT(success);
return success;
}
void ConPty::stop() {
if (m_pipeThread) {
m_pipeThread->requestInterruption();
}
if (m_processInformation) {
uninstallWinProcessEventNotifier(m_processInformation->hProcess);
CloseHandle(m_processInformation->hThread);
CloseHandle(m_processInformation->hProcess);
}
if (m_startupInfo) {
DeleteProcThreadAttributeList(m_startupInfo->lpAttributeList);
free(m_startupInfo->lpAttributeList);
}
if (m_ptyHandler != INVALID_HANDLE_VALUE) {
#if COMPILE_CONPTY_ENABLED
ClosePseudoConsole(m_ptyHandler);
#endif
}
if (m_inPipe != INVALID_HANDLE_VALUE) {
CloseHandle(m_inPipe);
}
if (m_outPipe != INVALID_HANDLE_VALUE) {
CloseHandle(m_outPipe);
}
if (m_pipeThread) {
m_pipeThread->wait(1000);
m_pipeThread->deleteLater();
}
}
qint64 ConPty::write(const QByteArray &text) {
DWORD bytesWritten;
WriteFile(m_outPipe, text.data(), text.size(), &bytesWritten, NULL);
return bytesWritten;
}

66
src/Pty/ConPty.h Normal file
View File

@@ -0,0 +1,66 @@
/*
* Copyright 2020, WindTerm.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
#ifndef CONPTY_H
#define CONPTY_H
#pragma once
#include "Pty.h"
#ifdef Q_OS_WIN
#include <windows.h>
#endif // Q_OS_WIN
class PipeThread;
class ConPty
: public Pty
{
Q_OBJECT
public:
ConPty(QObject *parent = nullptr);
virtual ~ConPty();
void appendBuffer(const QByteArray &buffer);
bool createProcess(QString command, const QString &arguments,
const QString &workingDirectory, const QStringList &environment,
qint16 rows, qint16 columns) final;
static bool isAvailable();
QByteArray readAll() final;
bool resizeWindow(qint16 rows, qint16 columns) final;
qint64 write(const QByteArray &text) final;
private:
HRESULT createPseudoConsoleAndPipes(HPCON *phPC, HANDLE *phPipeIn, HANDLE *phPipeOut, qint16 rows, qint16 columns);
HRESULT initStartupInfoAttachedToPseudoConsole(STARTUPINFOEX *pStartupInfo, HPCON hPC);
void stop();
private:
Q_DISABLE_COPY(ConPty)
QByteArray m_buffer;
PipeThread *m_pipeThread;
HANDLE m_inPipe;
HANDLE m_outPipe;
HPCON m_ptyHandler;
std::unique_ptr<PROCESS_INFORMATION> m_processInformation;
std::unique_ptr<STARTUPINFOEX> m_startupInfo;
};
#endif // CONPTY_H

95
src/Pty/Pty.cpp Normal file
View File

@@ -0,0 +1,95 @@
/*
* Copyright 2020, WindTerm.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 "Pty.h"
#ifdef Q_OS_WIN
#include <windows.h>
#include <QWinEventNotifier>
#endif // Q_OS_WIN
Pty::Pty()
: m_columns(-1)
, m_errorCode(0)
, m_rows(-1)
#ifdef Q_OS_WIN
, m_winProcessEventNotifier(nullptr)
#endif
{}
QString Pty::errorString() {
ThreadLocker<SpinMutex> locker(m_mutex);
return std::move(m_errorString);
}
#ifdef Q_OS_WIN
void Pty::installWinProcessEventNotifier(void *handle) {
if (m_winProcessEventNotifier == nullptr) {
m_winProcessEventNotifier = new QWinEventNotifier(handle, this);
connect(m_winProcessEventNotifier, &QWinEventNotifier::activated, this, [this](HANDLE handle) {
if (handle) {
DWORD exitCode;
if (GetExitCodeProcess(handle, &exitCode)) {
setErrorString(QString("Process exited with code %1").arg(
QString::number(exitCode, (exitCode >= 0xFF) ? 16 : 10).prepend((exitCode >= 0xFF) ? "0x" : "")
));
}
m_winProcessEventNotifier->setEnabled(false);
}
});
}
if (m_winProcessEventNotifier->handle() != handle) {
m_winProcessEventNotifier->setHandle(handle);
m_winProcessEventNotifier->setEnabled(true);
}
}
#endif
void Pty::setErrorCode(int errorCode) {
constexpr int bufferLength = 512;
wchar_t buffer[bufferLength];
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, errorCode,
LANG_NEUTRAL, buffer, bufferLength, NULL);
QString lastError = QString::fromWCharArray(buffer);
setErrorString(lastError);
m_errorCode = errorCode;
}
void Pty::setErrorString(const QString &errorString) {
if (errorString.isEmpty() == false) {
{
ThreadLocker<SpinMutex> locker(m_mutex);
m_errorString = errorString;
}
emit errorOccurred();
}
}
#ifdef Q_OS_WIN
void Pty::uninstallWinProcessEventNotifier(void *handle) {
if (m_winProcessEventNotifier != nullptr
&& m_winProcessEventNotifier->handle() == handle) {
m_winProcessEventNotifier->deleteLater();
m_winProcessEventNotifier = nullptr;
}
}
#endif

73
src/Pty/Pty.h Normal file
View File

@@ -0,0 +1,73 @@
/*
* Copyright 2020, WindTerm.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
#ifndef PTY_H
#define PTY_H
#include <QObject>
#include "Public/Spin.h"
class QWinEventNotifier;
class Pty
: public QObject
{
Q_OBJECT
public:
Pty();
virtual ~Pty() = default;
virtual bool createProcess(QString command, const QString &arguments,
const QString &workingDirectory, const QStringList &environment,
qint16 rows, qint16 columns) = 0;
int errorCode() const { return m_errorCode; }
QString errorString();
virtual QByteArray readAll() = 0;
virtual bool resizeWindow(qint16 rows, qint16 columns) = 0;
void setErrorCode(int errorCode);
void setErrorString(const QString &errorString);
virtual qint64 write(const QByteArray &text) = 0;
protected:
#ifdef Q_OS_WIN
void installWinProcessEventNotifier(void *handle);
void uninstallWinProcessEventNotifier(void *handle);
#endif
Q_SIGNALS:
void errorOccurred();
void readyRead();
protected:
SpinMutex m_mutex;
qint16 m_columns;
qint16 m_rows;
private:
Q_DISABLE_COPY(Pty)
int m_errorCode;
QString m_errorString;
#ifdef Q_OS_WIN
QWinEventNotifier *m_winProcessEventNotifier;
#endif
};
#endif // PTY_H

211
src/Pty/WinPty.cpp Normal file
View File

@@ -0,0 +1,211 @@
/*
* Copyright 2020, WindTerm.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 "WinPty.h"
#include <QFileInfo>
#include <QLocalSocket>
#include <QCoreApplication>
#include <sstream>
const char *WINPTY_AGENT_NAME = "winpty-agent.exe";
const char *WINPTY_DLL_NAME = "winpty.dll";
QString castErrorToString(winpty_error_ptr_t error_ptr) {
return QString::fromStdWString(winpty_error_msg(error_ptr));
}
WinPty::WinPty(QObject *parent /*= nullptr*/)
: m_ptyHandler(nullptr)
, m_innerHandle(INVALID_HANDLE_VALUE)
, m_outSocket(nullptr)
, m_inSocket(nullptr)
{}
WinPty::~WinPty() {
stop();
}
bool WinPty::createProcess(QString command, const QString &arguments,
const QString &workingDirectory, const QStringList &environment,
qint16 rows, qint16 columns) {
bool success = false;
winpty_error_ptr_t errorPtr = nullptr;
QString errorString;
do {
stop();
if (isAvailable() == false) {
errorString = tr("Winpty-agent.exe or winpty.dll not found!.");
break;
}
command = command.trimmed();
if (command.startsWith("\"") && command.endsWith("\"")) {
command.remove(command.length() - 1, 1);
command.remove(0, 1);
}
QString commandWithArguments = command;
if (commandWithArguments.contains(' ')) {
commandWithArguments.prepend("\"").append("\"");
}
if (arguments.isEmpty() == false) {
commandWithArguments.append(" ").append(arguments);
}
std::wstring env = environment.join(QChar('\0')).append(QChar('\0')).toStdWString();
winpty_config_t* startConfig = winpty_config_new(0, &errorPtr);
if (startConfig == nullptr) {
errorString = QString("WinPty Error: create start config -> %1").arg(castErrorToString(errorPtr));
break;
}
winpty_config_set_initial_size(startConfig, columns, rows);
winpty_config_set_mouse_mode(startConfig, WINPTY_MOUSE_MODE_AUTO);
m_ptyHandler = winpty_open(startConfig, &errorPtr);
winpty_config_free(startConfig);
if (m_ptyHandler == nullptr) {
errorString = QString("WinPty Error: start agent -> %1").arg(castErrorToString(errorPtr));
break;
}
QString m_conInName = QString::fromWCharArray(winpty_conin_name(m_ptyHandler));
QString m_conOutName = QString::fromWCharArray(winpty_conout_name(m_ptyHandler));
m_outSocket = std::make_unique<QLocalSocket>();
m_inSocket = std::make_unique<QLocalSocket>();
m_outSocket->connectToServer(m_conInName, QIODevice::WriteOnly);
m_outSocket->waitForConnected();
m_inSocket->connectToServer(m_conOutName, QIODevice::ReadOnly);
m_inSocket->waitForConnected();
if (m_outSocket->state() != QLocalSocket::ConnectedState
&& m_inSocket->state() != QLocalSocket::ConnectedState) {
errorString = QString("WinPty Error: Unable to connect local sockets -> %1 / %2")
.arg(m_outSocket->errorString())
.arg(m_inSocket->errorString());
m_inSocket.reset(nullptr);
m_outSocket.reset(nullptr);
break;
}
connect(m_inSocket.get(), &QLocalSocket::readyRead, this, [this]() {
emit readyRead();
});
winpty_spawn_config_t* spawnConfig = winpty_spawn_config_new(
WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN,
command.toStdWString().c_str(),
commandWithArguments.toStdWString().c_str(),
workingDirectory.isEmpty() ? NULL : workingDirectory.toStdWString().c_str(),
env.c_str(),
&errorPtr
);
if (spawnConfig == nullptr) {
errorString = QString("WinPty Error: create spawn config -> %1").arg(castErrorToString(errorPtr));
break;
}
BOOL spawnSuccess = winpty_spawn(m_ptyHandler, spawnConfig, &m_innerHandle, nullptr, nullptr, &errorPtr);
winpty_spawn_config_free(spawnConfig);
if (spawnSuccess == FALSE) {
errorString = QString("WinPty Error: start terminal process -> %1").arg(castErrorToString(errorPtr));
break;
}
success = true;
} while (0);
if (errorString.isEmpty() == false) {
Q_ASSERT(success == false);
winpty_error_free(errorPtr);
setErrorString(errorString);
}
if (success) {
m_columns = columns;
m_rows = rows;
installWinProcessEventNotifier(m_innerHandle);
}
return success;
}
QByteArray WinPty::readAll() {
QByteArray buffer;
if (m_inSocket) {
buffer = m_inSocket->readAll();
Q_ASSERT(buffer.isEmpty() == false);
}
return buffer;
}
bool WinPty::resizeWindow(qint16 rows, qint16 columns) {
bool success = true;
if (rows != m_rows && columns != m_columns) {
success = m_ptyHandler ? winpty_set_size(m_ptyHandler, columns, rows, nullptr) : false;
if (success) {
m_rows = rows;
m_columns = columns;
}
}
Q_ASSERT(success);
return success;
}
void WinPty::stop() {
if (m_ptyHandler != nullptr) {
winpty_free(m_ptyHandler);
m_ptyHandler = nullptr;
}
if (m_innerHandle != INVALID_HANDLE_VALUE) {
uninstallWinProcessEventNotifier(m_innerHandle);
CloseHandle(m_innerHandle);
m_innerHandle = INVALID_HANDLE_VALUE;
}
m_outSocket.reset(nullptr);
m_inSocket.reset(nullptr);
}
qint64 WinPty::write(const QByteArray &text) {
qint64 bytesWritten = -1;
if (m_outSocket) {
bytesWritten = m_outSocket->write(text);
Q_ASSERT(bytesWritten != -1);
}
return bytesWritten;
}
bool WinPty::isAvailable() {
return QFile::exists(QCoreApplication::applicationDirPath() + "/" + WINPTY_AGENT_NAME)
&& QFile::exists(QCoreApplication::applicationDirPath() + "/" + WINPTY_DLL_NAME);
}

61
src/Pty/WinPty.h Normal file
View File

@@ -0,0 +1,61 @@
/*
* Copyright 2020, WindTerm.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
#ifndef WINPTY_H
#define WINPTY_H
#pragma once
#include "Pty.h"
#ifdef Q_OS_WIN
#include <windows.h>
#endif // Q_OS_WIN
#include "winpty_api.h"
class QLocalSocket;
class WinPty
: public Pty
{
Q_OBJECT
public:
WinPty(QObject *parent = nullptr);
virtual ~WinPty();
bool createProcess(QString command, const QString &arguments,
const QString &workingDirectory, const QStringList &environment,
qint16 rows, qint16 columns) final;
static bool isAvailable();
QByteArray readAll() final;
bool resizeWindow(qint16 rows, qint16 columns) final;
qint64 write(const QByteArray &text) final;
private:
void stop();
private:
Q_DISABLE_COPY(WinPty)
winpty_t *m_ptyHandler;
HANDLE m_innerHandle;
std::unique_ptr<QLocalSocket> m_inSocket;
std::unique_ptr<QLocalSocket> m_outSocket;
};
#endif // WINPTY_H

21
src/Pty/ptyqt LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Vitaly Petrov
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,25 +1,47 @@
**All source codes (except thirdparty directory) are provided under the terms of Apache-2.0 license.**
**Important: WindTerm is a partial open source project, and the source will be gradually opened.**
**All released source codes (except thirdparty directory) are provided under the terms of Apache-2.0 license.**
# Components
Below is a list of (some) WindTerm components in alphabetical order, along with a brief description of each.
## CircularBuffer.h
A quick circular buffer template class.
## Cryptographic.h/cpp
A very safe encryption class using the PBKDF2-algorithm as defined in RFC 8018. WindTerm uses this class together with the user's master password to protect user data, including passwords, private keys and so on.
## Onigmo
An improved version based on Onigmo 5.13.5. In particular, **the addition of iterator makes it possible to match gap buffer or nonadjacent memory blocks.** Please refer to the sample files for how to use.
## ScopeGuard.h
## Pty
An improved version based on [ptyqt](https://github.com/kafeg/ptyqt). **Almost all the code was rewritten to make the pty more robust and stable.**
## Protocol/TelentProtocol.h/cpp
An implementation of Telnet protocol.
## Utility/CircularBuffer.h
A quick circular buffer template class.
## Utility/Cryptographic.h/cpp
A very safe encryption class using the PBKDF2-algorithm as defined in RFC 8018. WindTerm uses this class together with the user's master password to protect user data, including passwords, private keys and so on.
## Utility/ScopeGuard.h
A class of which the sole purpose is to run the function f in its destructor. This is useful for guaranteeing your cleanup code is executed.
## Spin.h
## Utility/Spin.h
A high-performance spin mutex and locker.
A high-performance spin mutex and locker.
## Utility/ThreadLocal.h/cpp
A high-performance thread local storage.
## Widgets/PopupWidget.h/cpp
A popup widget.
## Widgets/Scrollbar.h/cpp
A scrollbar supports 64-bit ranges.

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2020, WindTerm.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 "ThreadLocal.h"
#include <QThread>
void ThreadLocalWatcher::stopWatchCurrentThread() {
QThread::currentThread()->disconnect(this);
}
void ThreadLocalWatcher::watchCurrentThread() {
boost::thread::id id = boost::this_thread::get_id();
connect(QThread::currentThread(), &QThread::finished, this, [this, id]() {
clear(id);
});
}

205
src/Utility/ThreadLocal.h Normal file
View File

@@ -0,0 +1,205 @@
/*
* Copyright 2020, WindTerm.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
#ifndef THREADLOCAL_H
#define THREADLOCAL_H
#include <QObject>
#include <mutex>
#include <vector>
#include <boost/thread.hpp>
#include "Spin.h"
#define STATIC_LOCAL_COUNT 6
class ThreadLocalWatcher : public QObject {
Q_OBJECT
public:
ThreadLocalWatcher() = default;
protected:
void stopWatchCurrentThread();
void watchCurrentThread();
virtual void clear(const boost::thread::id &id) = 0;
private:
Q_DISABLE_COPY(ThreadLocalWatcher)
};
template <typename T>
class ThreadLocal : public ThreadLocalWatcher {
public:
ThreadLocal()
: m_mainThreadId(boost::this_thread::get_id())
{
for (int i = 0; i < STATIC_LOCAL_COUNT; i++) {
m_staticLocals[i] = nullptr;
}
}
~ThreadLocal() {
clearAll();
}
void clear(const boost::thread::id &id) {
std::lock_guard<SpinMutex> lock(m_mutex);
if (removeLocal(id)) {
stopWatchCurrentThread();
}
}
void clearAll() {
std::lock_guard<SpinMutex> lock(m_mutex);
for (int i = 0; i < STATIC_LOCAL_COUNT; i++) {
if (Local *local = m_staticLocals[i]) {
delete local;
m_staticLocals[i] = nullptr;
}
}
if (m_dynamicLocals) {
for (Local *local : *m_dynamicLocals) {
delete local;
}
m_dynamicLocals.reset(nullptr);
}
}
T *get() {
boost::thread::id id = boost::this_thread::get_id();
for (const Local *local : m_staticLocals) {
if (local && local->id == id) {
return local->ptr;
}
}
if (m_dynamicLocals) {
std::lock_guard<SpinMutex> lock(m_mutex);
for (const Local *local : *m_dynamicLocals) {
if (local && local->id == id) {
return local->ptr;
}
}
}
return nullptr;
}
boost::thread::id mainThreadId() const {
return m_mainThreadId;
}
void reset(T *t) {
std::lock_guard<SpinMutex> lock(m_mutex);
boost::thread::id id = boost::this_thread::get_id();
bool removed = removeLocal(id);
bool added = addLocal(id, t);
if (id != m_mainThreadId) {
if (removed) {
if (added == false) {
stopWatchCurrentThread();
}
} else {
if (added) {
watchCurrentThread();
}
}
}
}
private:
bool addLocal(const boost::thread::id &id, T *t) {
if (t == nullptr) {
return false;
}
for (int i = 0; i < STATIC_LOCAL_COUNT; i++) {
if (m_staticLocals[i] == nullptr) {
m_staticLocals[i] = new Local(id, t);
return true;
}
}
if (m_dynamicLocals.get() == nullptr) {
m_dynamicLocals = std::make_unique<LocalVector>();
}
m_dynamicLocals->push_back(new Local(id, t));
return true;
}
bool removeLocal(const boost::thread::id &id) {
for (int i = 0; i < STATIC_LOCAL_COUNT; i++) {
Local *local = m_staticLocals[i];
if (local && local->id == id) {
delete local;
m_staticLocals[i] = nullptr;
return true;
}
}
if (m_dynamicLocals) {
for (auto it = m_dynamicLocals->begin(); it != m_dynamicLocals->end(); ++it) {
Local *local = *it;
if (local && local->id == id) {
delete local;
m_dynamicLocals->erase(it);
return true;
}
}
}
return false;
}
private:
Q_DISABLE_COPY(ThreadLocal)
struct Local {
boost::thread::id id;
T *ptr;
Local(boost::thread::id id_, T *ptr_)
: id(id_)
, ptr(ptr_)
{}
~Local() {
delete ptr;
ptr = nullptr;
}
};
typedef std::vector<Local *> LocalVector;
std::unique_ptr<LocalVector> m_dynamicLocals;
Local *m_staticLocals[STATIC_LOCAL_COUNT];
boost::thread::id m_mainThreadId;
SpinMutex m_mutex;
};
#endif // THREADLOCAL_H

226
src/Widgets/PopupWidget.cpp Normal file
View File

@@ -0,0 +1,226 @@
/*
* Copyright 2020, WindTerm.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 "PopupWidget.h"
#include <QAction>
#include <QApplication>
#include <QDesktopWidget>
#include <QMouseEvent>
#include <QStyle>
#include <QToolButton>
#include <QVBoxLayout>
#include <private/qeffects_p.h>
#define TRIANGLE_HEIGHT 18
PopupWidget::PopupWidget(QWidget *parent /*= nullptr*/)
: QFrame(parent)
{
setWindowFlags(Qt::Window
| Qt::FramelessWindowHint
| Qt::WindowStaysOnTopHint
| Qt::X11BypassWindowManagerHint
| Qt::WindowDoesNotAcceptFocus);
setAttribute(Qt::WA_ShowWithoutActivating, true);
setAttribute(Qt::WA_X11DoNotAcceptFocus, true);
setAttribute(Qt::WA_DeleteOnClose, true);
setFocusPolicy(Qt::NoFocus);
setFrameShape(QFrame::StyledPanel);
setMouseTracking(true);
createLayout();
}
PopupWidget::~PopupWidget() {
if (QAbstractButton *button = dynamic_cast<QAbstractButton *>(parent())) {
if (QToolButton *toolButton = dynamic_cast<QToolButton *>(parent())) {
if (QAction *action = toolButton->defaultAction()) {
action->setChecked(false);
}
}
button->setChecked(false);
}
// Kill any running effect
qFadeEffect(0);
}
void PopupWidget::createLayout() {
QVBoxLayout *vBoxLayout = new QVBoxLayout;
vBoxLayout->setSpacing(0);
setLayout(vBoxLayout);
}
bool PopupWidget::eventFilter(QObject *watched, QEvent *event) {
switch (event->type()) {
case QEvent::FocusOut:
if (QWidget *widget = dynamic_cast<QWidget *>(watched)) {
if (isAncestorOf(widget) == false && QApplication::activePopupWidget() == nullptr) {
bool hasMenuAncestor = false;
do {
if (widget->inherits("QMenu")) {
hasMenuAncestor = true;
break;
}
} while (widget = widget->parentWidget());
if (hasMenuAncestor == false) {
close();
}
}
}
break;
case QEvent::KeyPress: {
if (QKeyEvent *keyEvent = dynamic_cast<QKeyEvent *>(event)) {
QWidget *activePopupWidget = QApplication::activePopupWidget();
if (keyEvent->matches(QKeySequence::Cancel)) {
if (activePopupWidget) {
activePopupWidget->close();
} else {
close();
}
return true;
} else if (isAncestorOf(QApplication::focusWidget()) == false) {
if (activePopupWidget == nullptr) {
close();
}
}
}
break;
}
case QEvent::NonClientAreaMouseButtonPress:
case QEvent::WindowStateChange:
close();
break;
case QEvent::MouseButtonPress: {
if (QMouseEvent *mouseEvent = dynamic_cast<QMouseEvent *>(event)) {
do {
QPoint globalPos = mouseEvent->globalPos();
if (rect().contains(mapFromGlobal(globalPos)))
break;
if (QAbstractButton *button = dynamic_cast<QAbstractButton *>(parentWidget())) {
if (button->rect().contains(button->mapFromGlobal(globalPos))) {
break;
}
}
setAttribute(Qt::WA_NoMouseReplay);
close();
} while (0);
}
break;
}
case QEvent::WindowDeactivate: {
if (QWidget *parentWidget = this->parentWidget()) {
if (parentWidget->isAncestorOf(QApplication::focusWidget()) == false) {
close();
}
}
break;
}
}
return false;
}
void PopupWidget::mousePressEvent(QMouseEvent *event) {
setAttribute(Qt::WA_NoMouseReplay);
QWidget::mousePressEvent(event);
}
void PopupWidget::setCentralWidget(QWidget *widget) {
widget->layout()->setContentsMargins(QMargins());
widget->setParent(this);
layout()->addWidget(widget);
adjustSize();
}
void PopupWidget::show(Area hArea, Area vArea) {
Q_ASSERT(parentWidget() != nullptr);
if (QWidget *parent = parentWidget()) {
QPoint newPos;
QPolygon triPolygon;
QRect rectPolygon;
QPoint pos = parent->mapToGlobal(QPoint());
QRect rect = parent->rect();
QRect screenRect = QApplication::desktop()->availableGeometry(this);
int topMargin = style()->pixelMetric(QStyle::PM_LayoutTopMargin);
int bottomMargin = style()->pixelMetric(QStyle::PM_LayoutBottomMargin);
int leftMargin = style()->pixelMetric(QStyle::PM_LayoutLeftMargin);
int rightMargin = style()->pixelMetric(QStyle::PM_LayoutRightMargin);
int triangleHeight = std::max(TRIANGLE_HEIGHT, topMargin);
if (pos.y() < height()) {
if (vArea == TopArea) {
vArea = BottomArea;
}
} else {
if (vArea == BottomArea && screenRect.height() - pos.y() - rect.height() < height()) {
vArea = TopArea;
}
}
if (hArea == MiddleArea) {
newPos.setX(qBound(0, pos.x() + (rect.width() - width()) / 2, screenRect.width() - width()));
} else {
if (hArea == LeftArea) {
if (pos.x() + rect.width() < width()) {
hArea = RightArea;
}
} else {
if (screenRect.width() - pos.x() < width()) {
hArea = LeftArea;
}
}
newPos.setX((hArea == LeftArea) ? pos.x() + rect.width() - width() : pos.x());
}
newPos.setY((vArea == BottomArea) ? pos.y() + rect.height() : pos.y() - height() - triangleHeight);
int xCenter = pos.x() + (rect.width() / 2) - newPos.x();
if (vArea == TopArea) {
triPolygon << QPoint(xCenter - triangleHeight, height())
<< QPoint(xCenter, height() + triangleHeight)
<< QPoint(xCenter + triangleHeight, height());
rectPolygon = QRect(0, 0, width(), height());
layout()->setContentsMargins(leftMargin, topMargin, rightMargin, bottomMargin + triangleHeight * 2);
} else {
triPolygon << QPoint(xCenter - triangleHeight, triangleHeight)
<< QPoint(xCenter, 0)
<< QPoint(xCenter + triangleHeight, triangleHeight);
rectPolygon = QRect(0, triangleHeight, width(), height());
layout()->setContentsMargins(leftMargin, topMargin + triangleHeight, rightMargin, bottomMargin);
}
QRegion triangle(triPolygon);
QRegion rectangle(rectPolygon, QRegion::Rectangle);
QRegion mask = rectangle.united(triangle);
setMask(mask);
move(newPos);
QWidget::show();
qFadeEffect(this, 200);
qApp->installEventFilter(this);
}
}

47
src/Widgets/PopupWidget.h Normal file
View File

@@ -0,0 +1,47 @@
/*
* Copyright 2020, WindTerm.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
#ifndef POPUPWIDGET_H
#define POPUPWIDGET_H
#pragma once
#include <QFrame>
class PopupWidget : public QFrame
{
Q_OBJECT
public:
explicit PopupWidget(QWidget *parent = nullptr);
virtual ~PopupWidget();
enum Area { LeftArea, MiddleArea, RightArea, TopArea, BottomArea };
public:
void setCentralWidget(QWidget *widget);
void show(Area hArea, Area vArea);
private:
void createLayout();
bool eventFilter(QObject *watched, QEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
private:
Q_DISABLE_COPY(PopupWidget)
};
#endif // POPUPWIDGET_H

153
src/Widgets/ScrollBar.cpp Normal file
View File

@@ -0,0 +1,153 @@
/*
* Copyright 2020, WindTerm.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 "ScrollBar.h"
#include <QEvent>
#include <QVariant>
ScrollBar::ScrollBar(Qt::Orientation orientation, QWidget *parent /*= nullptr*/)
: QScrollBar(orientation, parent)
{
m_maximum = QScrollBar::maximum();
m_minimum = QScrollBar::minimum();
m_value = QScrollBar::value();
setContextMenuPolicy(Qt::PreventContextMenu);
createConnections();
}
void ScrollBar::createConnections() {
connect(this, &QScrollBar::valueChanged, this, [this](qint64 value) {
if (m_maximum > INT_MAX) {
value *= (double)m_maximum / INT_MAX;
}
if (m_value != value) {
qint64 oldValue = m_value;
m_value = value;
emit valueChanged(m_value, oldValue);
}
});
connect(this, &QScrollBar::actionTriggered, this, [this](int action) {
qint64 oldValue = m_value;
switch (action) {
case QAbstractSlider::SliderMove:
if (m_maximum > INT_MAX) {
m_value = (QScrollBar::sliderPosition() * ((double)m_maximum / INT_MAX));
} else {
m_value = QScrollBar::sliderPosition();
}
break;
case QAbstractSlider::SliderPageStepAdd:
m_value = std::min(m_maximum, m_value + pageStep());
break;
case QAbstractSlider::SliderPageStepSub:
m_value = std::max(m_minimum, m_value - pageStep());
break;
case QAbstractSlider::SliderSingleStepAdd:
m_value = std::min(m_maximum, m_value + singleStep());
break;
case QAbstractSlider::SliderSingleStepSub:
m_value = std::max(m_minimum, m_value - singleStep());
break;
case QAbstractSlider::SliderToMaximum:
m_value = m_maximum;
break;
case QAbstractSlider::SliderToMinimum:
m_value = m_minimum;
break;
}
if (m_maximum > INT_MAX) {
Q_ASSERT(QScrollBar::maximum() == INT_MAX);
QScrollBar::setSliderPosition(m_value * ((double)INT_MAX / m_maximum));
}
if (m_value != oldValue) {
emit valueChanged(m_value, oldValue);
}
});
}
bool ScrollBar::setMaximum(qint64 maximum) {
return setRange(std::min(m_minimum, maximum), maximum);
}
bool ScrollBar::setMinimum(qint64 minimum) {
return setRange(minimum, std::max(minimum, m_maximum));
}
bool ScrollBar::setPage(int page) {
if (page > 0 && page != QScrollBar::pageStep()) {
QScrollBar::setPageStep(page);
emit pageChanged(page);
return true;
}
return false;
}
bool ScrollBar::setRange(qint64 minimum, qint64 maximum) {
if (minimum >= 0 && minimum <= maximum && (m_minimum != minimum || m_maximum != maximum)) {
m_maximum = maximum;
m_minimum = minimum;
QScrollBar::setRange(
(m_minimum > INT_MAX) ? INT_MAX : m_minimum,
(m_maximum > INT_MAX) ? INT_MAX : m_maximum
);
emit rangeChanged(minimum, maximum);
return true;
}
return false;
}
bool ScrollBar::setSingleStep(int singleStep) {
if (singleStep > 0 && singleStep != QScrollBar::singleStep()) {
QScrollBar::setSingleStep(singleStep);
emit singleStepChanged(singleStep);
return true;
}
return false;
}
bool ScrollBar::setValue(qint64 value) {
value = qBound(m_minimum, value, m_maximum);
if (m_value != value) {
qint64 oldValue = m_value;
m_value = value;
if (m_maximum > INT_MAX) {
value *= (double)INT_MAX / m_maximum;
}
{
QSignalBlocker blocker(this);
QScrollBar::setValue(value);
}
emit valueChanged(m_value, oldValue);
return true;
}
return false;
}

59
src/Widgets/ScrollBar.h Normal file
View File

@@ -0,0 +1,59 @@
/*
* Copyright 2020, WindTerm.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
#ifndef SCROLLBAR_H
#define SCROLLBAR_H
#pragma once
#include <QScrollBar>
class ScrollBar : public QScrollBar
{
Q_OBJECT
public:
explicit ScrollBar(Qt::Orientation orientation, QWidget *parent = nullptr);
virtual ~ScrollBar() = default;
qint64 maximum() const { return m_maximum; }
qint64 minimum() const { return m_minimum; }
bool setMaximum(qint64 maximum);
bool setMinimum(qint64 minimum);
bool setPage(int page);
bool setRange(qint64 minimum, qint64 maximum);
bool setSingleStep(int singleStep);
bool setValue(qint64 value);
qint64 value() const { return m_value; }
private:
void createConnections();
Q_SIGNALS:
void pageChanged(int page) const;
void rangeChanged(qint64 minimum, qint64 maximum) const;
void singleStepChanged(int singleStep) const;
void valueChanged(qint64 value, qint64 oldValue) const;
private:
Q_DISABLE_COPY(ScrollBar)
qint64 m_maximum;
qint64 m_minimum;
qint64 m_value;
};
#endif // SCROLLBAR_H