mirror of
https://github.com/kingToolbox/WindTerm.git
synced 2026-08-18 10:33:28 +08:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
150635107e | ||
|
|
8a0d506c52 | ||
|
|
fc213aa6fe | ||
|
|
86359ef6a4 | ||
|
|
589a28a22d | ||
|
|
a5ab332b20 | ||
|
|
fdc12226d7 | ||
|
|
8f73b75e79 | ||
|
|
2e76fa927c | ||
|
|
78e57fb850 | ||
|
|
cf08fe14ca | ||
|
|
685cf3111d | ||
|
|
d1eea0c9ef | ||
|
|
f5232e9226 | ||
|
|
d5c33239e5 | ||
|
|
23570ead27 |
29
README.md
29
README.md
@@ -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:
|
||||
|
||||

|
||||
|
||||
Quick Bar:
|
||||
|
||||

|
||||
|
||||
Split views:
|
||||
|
||||

|
||||
|
||||
DigeWhite Theme:
|
||||
|
||||

|
||||
|
||||
|
||||
# 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 September,for reference only):**
|
||||
- Auto complete
|
||||
**Next release (Early October,for 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 |
BIN
images/screenshots/WindTerm_DigeWhite_Theme.png
Normal file
BIN
images/screenshots/WindTerm_DigeWhite_Theme.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 314 KiB |
36
src/Protocols/Protocol.h
Normal file
36
src/Protocols/Protocol.h
Normal 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
|
||||
93
src/Protocols/TelnetProtocol.cpp
Normal file
93
src/Protocols/TelnetProtocol.cpp
Normal 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";
|
||||
}
|
||||
206
src/Protocols/TelnetProtocol.h
Normal file
206
src/Protocols/TelnetProtocol.h
Normal 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
261
src/Pty/ConPty.cpp
Normal 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
66
src/Pty/ConPty.h
Normal 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
95
src/Pty/Pty.cpp
Normal 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
73
src/Pty/Pty.h
Normal 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
211
src/Pty/WinPty.cpp
Normal 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
61
src/Pty/WinPty.h
Normal 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
21
src/Pty/ptyqt LICENSE
Normal 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.
|
||||
@@ -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.
|
||||
|
||||
31
src/Utility/ThreadLocal.cpp
Normal file
31
src/Utility/ThreadLocal.cpp
Normal 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
205
src/Utility/ThreadLocal.h
Normal 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
226
src/Widgets/PopupWidget.cpp
Normal 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
47
src/Widgets/PopupWidget.h
Normal 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
153
src/Widgets/ScrollBar.cpp
Normal 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
59
src/Widgets/ScrollBar.h
Normal 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
|
||||
Reference in New Issue
Block a user