├── README.md ├── multiX ├── README.md ├── augmentation │ ├── Makefile │ ├── README.md │ ├── aug.c │ └── aug.so ├── display │ ├── README.md │ ├── daemon.py │ ├── multix.py │ ├── multixConfig.py │ ├── multixDefines.py │ └── multixGUI.py ├── run.sh └── screenshots │ ├── fmview.png │ ├── original_fmview.png │ ├── original_textview.png │ └── textview.png ├── ramVis ├── Makefile ├── README.md ├── ramvis ├── ramvis.c └── screenshots │ ├── original.png │ └── ramvis.png ├── requirements.txt ├── screenshots └── unixporn.png └── traceAnim ├── Makefile ├── README.md ├── screenshots └── traceanim.mp4 ├── traceAnim └── traceAnim.c /README.md: -------------------------------------------------------------------------------- 1 | # HacknetGTK is a project that brings some of the Hacknet GUI elements to linux-based systems using GTK 2 | 3 | ![screenshot of desktop](./screenshots/unixporn.png) 4 | 5 | ### Projects that are already implemeneted or planned: 6 | * traceAnim - the famous trace animation 7 | * ramVis - ram usage visualization app 8 | * multiX - the DISPLAY from the original game. File manager, text and image viewer, and a network protocol to bind the GUI to a terminal shell 9 | 10 | ### Build dependencies 11 | 12 | Python dependencies are in `requirements.txt` (install with `pip3 install -r requirements.txt`). 13 | 14 | Install dependencies on: 15 | **Arch**: 16 | `sudo pacman -S binutils gcc make cairo gtk2 gtk3` 17 | **Debian**: 18 | `sudo apt install binutils gcc make libcairo2 libcairo2-dev gtk+2.0 gtk+3.0` 19 | 20 | ### Aesthetics notice 21 | 22 | The whole project was developed under GNOME 3. If you intend to use the components with a tiling wm, things may look bad due to the way tiling managers allocate screen space. 23 | Also, the text elements are entirely configured by your global gtk theme. 24 | 25 | I advise to resize windows manually or using wm-specific rules, configure some colors for consistency, and install a dark gtk theme (i recommend adwaita-dark). 26 | -------------------------------------------------------------------------------- /multiX/README.md: -------------------------------------------------------------------------------- 1 | # MultiX - hacknet file manager remake 2 | 3 | This is a remake of "DISPLAY" and "TERMINAL" windows from Hacknet. There are three parts to this project. 4 | Different components have more detailed descriptions in their own directories. 5 | 6 | To run, configure some variables in `./display/multixConfig.py` and run the whole thing with `./run.sh`. 7 | 8 | ## Part 1: The GUI 9 | 10 | The GUI is in `./display`. It is an interface, that mimics the original Hacknet DISPLAY window. 11 | GUI is written entirely in python and needs `python-magic`. 12 | Some configuration value are available in `./display/multixConfig.py`. (you will at least want to change `TOPLEVEL_PATH`value). 13 | 14 | You can run it separately with `./display/daemon.py`. 15 | 16 | ## Part 2: The shell augmentation library 17 | 18 | To bind the GUI to a terminal shell process, a shared library is implemented. 19 | It is injected into the shell process with LD_PRELOAD. 20 | 21 | The library should work with most linux shells (tested on bash, zsh, and dash). 22 | 23 | Build it with `make lib`, run it with `make run`. You can configure the shell (bash by default) in the `./augmentation/Makefile`. 24 | 25 | ## Part 3: The communication protocol 26 | 27 | The protocol that binds the augmented shell to GUI process. It uses unix sockets (`/tmp/multix.socket` by default). 28 | 29 | 30 | # Screenshots 31 | 32 | ## Original 33 | 34 | ![original window file manager view](./screenshots/original_fmview.png) 35 | ![original window text file view](./screenshots/original_textview.png) 36 | 37 | ## Project HacknetGTK 38 | 39 | 40 | ![file manager view](./screenshots/fmview.png) 41 | ![text file view](./screenshots/textview.png) 42 | 43 | # Final notice 44 | 45 | This component of HacknetGTK is not completely finished. It needs improvements. 46 | 47 | **A list of potential improvements and limitations**: 48 | 49 | * Make images in GUI scale, instead of opening in full size 50 | * Maybe, include functionality to open audio and video players in a subwindow 51 | * Files, opened in GUI will open in the terminal, but not the other way around 52 | -------------------------------------------------------------------------------- /multiX/augmentation/Makefile: -------------------------------------------------------------------------------- 1 | SHELL:=bash 2 | lib: 3 | gcc aug.c -o aug.so -shared -fPIC -pthread -ldl 4 | run: 5 | LD_PRELOAD=$(PWD)/aug.so $(SHELL) 6 | -------------------------------------------------------------------------------- /multiX/augmentation/README.md: -------------------------------------------------------------------------------- 1 | # multiX shell augmentation library 2 | 3 | This is the implementation of the "TERMINAL" window from hacknet for HacknetGTK project. 4 | I tried many different ways to augment the shell, while keeping it usable. This is what I came up with. 5 | 6 | ## Hacknet vs this project functionality comparison 7 | 8 | * directory changes are synchronized between gui and terminal 9 | * opening a file in the gui will open it in the terminal 10 | * opening a file in the terminal will open it in the gui 11 | 12 | ## Inner workings 13 | 14 | The shared library is loaded with LD_PRELOAD, before the shell is launched. 15 | On load, it starts a separate thread and sets up the environment. All multiX-related code runs in this separate thread. 16 | 17 | The main event loop handles communication with the GUI and observation of the shell state. 18 | 19 | If a directory change occures in the shell process, it is communicated to GUI. 20 | If a GUI indicates a directory change, the shell process will change it's CWD too. 21 | 22 | The library supports launching commands, that GUI sends to it with `system()`. 23 | 24 | ## Limitations 25 | 26 | Due to it's nature (a shared library made for injections in generic interactive shell processes), this code is fairly brittle. 27 | Don't be surprised of its inconsistent or unstable behaviour, because this kind of functionality fits in the area of vile and dirty hacks, usually. 28 | 29 | **and it's a remake of an application from a game, what do you even expect from it? :P** 30 | -------------------------------------------------------------------------------- /multiX/augmentation/aug.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | /*-=-=-=CONSTANTS=-=-=-*/ 15 | #define PATH_BUF_SIZE 1024 16 | #define COM_BUF_SIZE 1024 17 | #define POLLING_INTERVAL 0.5 18 | const char * DISPLAY_SOCKET_PATH = "/tmp/multix.socket"; 19 | 20 | /*-=-=-=VARIABLES=-=-=-*/ 21 | int socketDescriptor = -1; 22 | extern char **environ; 23 | 24 | /*-=-=-=FUNCTIONS=-=-=-*/ 25 | bool handleCommand(char * command) { 26 | 27 | char * com = command; 28 | char * args; 29 | for (int i = 0; i 0) { 78 | return true; 79 | } 80 | else { 81 | return false; 82 | } 83 | } 84 | 85 | bool getCurrentCommand(const int src, char * dstBuffer, size_t n) { 86 | 87 | memset(dstBuffer, 0, COM_BUF_SIZE); 88 | 89 | int ret = recv(src, dstBuffer, n, 0); 90 | 91 | if (ret > 0) { 92 | return true; 93 | } 94 | else { 95 | return false; 96 | } 97 | 98 | } 99 | 100 | //main thread of the multix server 101 | //all added functionality runs from here 102 | void serverMain(void) { 103 | 104 | printf("Initializing the multix server\n"); 105 | 106 | /*Initialize some internal vars to track state*/ 107 | char lastPath[PATH_BUF_SIZE] = {0}; 108 | char currentPath[PATH_BUF_SIZE] = {0}; 109 | if(getcwd(lastPath, PATH_BUF_SIZE) == NULL) { 110 | fprintf(stderr, "Can't get cwd\n"); 111 | exit(1); 112 | } 113 | char commandBuffer[COM_BUF_SIZE] = {0}; 114 | 115 | /*Create a Unix Domain socket descriptor*/ 116 | 117 | socketDescriptor = socket(AF_UNIX, SOCK_STREAM, 0); 118 | if (socketDescriptor == -1) { 119 | fprintf(stderr, "Can't acquire a socket\n"); 120 | exit(1); 121 | } 122 | 123 | /*Initialize remote address*/ 124 | 125 | struct sockaddr_un addr; 126 | memset(&addr, 0, sizeof(struct sockaddr_un)); 127 | addr.sun_family = AF_UNIX; 128 | strncpy(addr.sun_path, DISPLAY_SOCKET_PATH, sizeof(addr.sun_path) - 1); 129 | int addrLen = strlen(addr.sun_path) + sizeof(addr.sun_family); 130 | 131 | /*Connect to DISPLAY server via Unix Domain sockets*/ 132 | 133 | if (connect(socketDescriptor, (struct sockaddr*)&addr, addrLen) == -1) { 134 | fprintf(stderr, "Could not connect to DISPLAY server socket\n"); 135 | exit(1); 136 | } 137 | 138 | /*Create a pollfd struct for polling the socket input*/ 139 | const struct pollfd socketPollFd = { 140 | .fd = socketDescriptor, 141 | .events = POLLIN 142 | }; 143 | 144 | useconds_t realPollingInterval = POLLING_INTERVAL * 1000000; //convert seconds to microseconds in whole numbers 145 | 146 | //main server event loop 147 | //only handle events here 148 | while (1) { 149 | 150 | if (checkIncomingCommands(&socketPollFd)) { 151 | 152 | if (getCurrentCommand(socketDescriptor, commandBuffer, COM_BUF_SIZE) != true) { 153 | fprintf(stderr, "Failed to get remote command\n"); 154 | exit(1); 155 | } 156 | if (handleCommand(commandBuffer) != true) { 157 | fprintf(stderr, "Failed to handle command: %s\n", commandBuffer); 158 | } 159 | 160 | } 161 | 162 | //update cwd; check for changes 163 | memset(currentPath, 0, PATH_BUF_SIZE); 164 | if (getcwd(currentPath, PATH_BUF_SIZE) == NULL) { 165 | fprintf(stderr, "Can't get cwd\n"); 166 | exit(1); 167 | } 168 | 169 | if (strcmp(currentPath, lastPath) != 0) { 170 | 171 | char outgoingCom[COM_BUF_SIZE] = {0}; 172 | snprintf(outgoingCom, COM_BUF_SIZE, "CWD %s", currentPath); 173 | 174 | //cwd changed; notify display 175 | sendCommand(socketDescriptor, outgoingCom, COM_BUF_SIZE); 176 | //update lastcwd 177 | memset(lastPath, 0, PATH_BUF_SIZE); 178 | strncpy(lastPath, currentPath, PATH_BUF_SIZE); 179 | 180 | } 181 | 182 | usleep(realPollingInterval); 183 | 184 | } 185 | 186 | } 187 | 188 | //constructor function. will run before main 189 | __attribute__((constructor)) 190 | void initialize (void) { 191 | 192 | //our library should only be injected into the shell process 193 | //shells are assumed to use and possibly override environment variable behaviour 194 | //therefore, we manually terminate LD_PRELOAD variable 195 | for (int i = 0;environ[i];i++) { 196 | 197 | if (strstr(environ[i], "LD_PRELOAD")) { 198 | printf("Modifying environ directly to get rid of LD_PRELOAD\n"); 199 | environ[i][0] = '\0'; 200 | } 201 | 202 | } 203 | 204 | //create a thread for the multix server 205 | //the whole process lives in its own thread and only sometimes interferes with global process resources 206 | pthread_t server; 207 | pthread_create(&server, NULL, serverMain, NULL); 208 | 209 | } 210 | 211 | //destructor function. will run after main 212 | __attribute__((destructor)) 213 | void cleanup (void) { 214 | 215 | //close the socket descriptor 216 | printf("Unloading the multix server\n"); 217 | close(socketDescriptor); 218 | system("reset"); 219 | 220 | } 221 | -------------------------------------------------------------------------------- /multiX/augmentation/aug.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/project-kaat/hacknetGTK/475d5f747343a6e6f9ff65cc7891cef4286c3b05/multiX/augmentation/aug.so -------------------------------------------------------------------------------- /multiX/display/README.md: -------------------------------------------------------------------------------- 1 | # DISPLAY 2 | 3 | This directory contains the GUI-side code of multiX. 4 | You can find configuration values in `multixConfig.py`. 5 | Run the gui with `./daemon.py`. 6 | 7 | ## Project files 8 | 9 | * multix.py - various functions that do the dirty work 10 | * multixConfig.py - configuration values, intended to be edited by the user 11 | * multixDefines.py - datatypes and protocol specification, intended to be included by multix components 12 | * multixGUI.py - the graphical interface style and logic 13 | * daemon.py - driver code, intended for running 14 | 15 | ## Description of some concepts 16 | 17 | **the TOPLEVEL_PATH configuration option** 18 | 19 | Top level path is a path, below which the GUI will not display directory structure. 20 | It can be any path, like "/", or your user's home directory. 21 | If you make a hidden directory your TOPLEVEL_PATH and not enable FM_SHOW_HIDDEN, GUI will not display any directories. 22 | 23 | **TERMINAL_FOPEN_COMMANDS** 24 | 25 | This dictionary controls gui-to-terminal file open events. (e.g you click on a file in gui; the command, specified for the filetype will run in the terminal). 26 | 27 | **filetypes** 28 | 29 | There are 5 possible types of files, defined in `multixDefines.enums` - text, image, audio, video, and binary. 30 | The filetypes are determined using libmagic. You can configure, which magic values indicate which filetype in `multixDefines.enums`. The format uses strings, that support shell-like wildcards. 31 | -------------------------------------------------------------------------------- /multiX/display/daemon.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import gi 3 | gi.require_version("Gtk", "3.0") 4 | from gi.repository import Gtk 5 | import sys 6 | import os 7 | import socket 8 | from select import select 9 | from time import sleep 10 | 11 | from multix import perr, dbgp 12 | from multixConfig import settings 13 | from multixDefines import enums, proto 14 | import multixGUI 15 | 16 | class multixServer(): 17 | 18 | def __init__(self, socketPath): 19 | 20 | self.socketPath = socketPath 21 | 22 | if os.path.exists(self.socketPath): 23 | dbgp("Trying to reclaim the socket path, because it already exists") 24 | os.remove(self.socketPath) 25 | 26 | self.sockfd = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 27 | self.sockfd.bind(self.socketPath) 28 | self.sockfd.listen(1) 29 | 30 | self.client, self.clientAddress = self.sockfd.accept() 31 | 32 | def canRead(self): 33 | 34 | canReadValue = False 35 | 36 | if len(select([self.client], [], [], 0)[0]) > 0: 37 | canReadValue = True 38 | 39 | return canReadValue 40 | 41 | def recvCommand(self): 42 | 43 | msg = self.client.recv(1024).strip(b"\x00").decode() 44 | if len(msg) == 0: 45 | #the other side disconnected; terminate the server 46 | perr("The terminal side disconnected from the socket. Quitting") 47 | daemonQuit(0) 48 | dbgp(f"Server received data: {msg}") 49 | 50 | command = self._parseCmd(msg) 51 | 52 | dbgp(f"command = {command}") 53 | return command 54 | 55 | def send(self, data): 56 | 57 | dbgp(f"Server sending data: {data}") 58 | 59 | self.client.send(data.encode()) 60 | 61 | def _parseCmd(self, data): 62 | 63 | cmd = list() 64 | 65 | splitData = data.partition(" ") 66 | 67 | try: 68 | cmd.append(data.partition(" ")[0]) 69 | cmd.append(data.partition(" ")[2]) 70 | except IndexError: 71 | return None 72 | 73 | return cmd 74 | 75 | def __del__(self): 76 | self.sockfd.close() 77 | if os.path.exists(self.socketPath): 78 | os.remove(self.socketPath) 79 | 80 | def daemonQuit(arg): 81 | server.__del__() 82 | Gtk.main_quit() 83 | sys.exit(0) 84 | 85 | #-=-=-=Main daemon + GUI loop=-=-=- 86 | 87 | if __name__ == "__main__": 88 | try: 89 | 90 | server = multixServer(settings.SOCKET_PATH) 91 | rootWin = multixGUI.displayWindow(server.send) 92 | rootWin.connect("destroy", daemonQuit) 93 | rootWin.show_all() 94 | 95 | while True: 96 | if server.canRead(): 97 | command = server.recvCommand() 98 | if command: 99 | com = command[0] 100 | args = command[1] 101 | dbgp(f"Processing a command: {com}; with args: {args}") 102 | if com == proto.MSG_CWD: 103 | rootWin.activePath = args 104 | rootWin.mode = enums.DISPLAY_MODE_FM 105 | rootWin.update() 106 | else: 107 | perr(f"Unimplemented command received: {com}") 108 | else: 109 | perr("Malformed command received") 110 | 111 | if Gtk.events_pending(): 112 | Gtk.main_iteration() 113 | else: 114 | sleep(0.05) 115 | 116 | except Exception as e: 117 | perr(f"Received an exception") 118 | print(e) 119 | server.__del__() 120 | Gtk.main_quit() 121 | sys.exit() 122 | -------------------------------------------------------------------------------- /multiX/display/multix.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from dataclasses import dataclass 4 | import os 5 | import magic 6 | from fnmatch import fnmatch 7 | 8 | from multixConfig import settings 9 | from multixDefines import fileSystemEntryData, enums 10 | 11 | 12 | def decomposePath(path, base): 13 | 14 | """ 15 | Pathwalker algorithm. This will transform a filesystem path and a base path into a fileSystemEntry format 16 | 17 | path is an absolute path to any file or directory which will be the stopping point. 18 | base is the absolute path to any directory which will be the toplevel (starting) point. 19 | Trailing slashes don't matter (thanks to convoluted execution flow i guess) 20 | An attempt of documentation has been made below... 21 | """ 22 | 23 | ###Basic input sanity checks### 24 | if len(base) < 1 or base[0] != "/": 25 | return None 26 | 27 | if len(path) < 1: 28 | return None 29 | ############################### 30 | 31 | ###Variable preparation### 32 | tablevel = 0 # this will represent the indentation level 33 | retList = list() # this is the list of fileSystemEntryData that will be returned 34 | processList = list() # this is the output of the first walking stage 35 | ########################## 36 | 37 | ###First walking stage### 38 | 39 | if path[-1] != "/": #append a trailing slash to path, if not present 40 | path += "/" 41 | 42 | if base not in path: 43 | return None 44 | 45 | #here we generate a list of directories and files that will be walked as absolute paths 46 | 47 | if base != "/": #base is not / (the algorithm removes base from path in this case) 48 | if base[-1] != "/": #no trailing slash in base (we need it to properly remove base from path) 49 | base += "/" #add the trailing slash 50 | path = path.replace(base, "") #remove base from path to insert it after the splitting 51 | base = base[:-1] #remove the trailing slash from base, as it is no longer wanted 52 | 53 | processList.append(base) #base is added as a toplevel directory to not go below it 54 | 55 | for section in path.split("/"): 56 | 57 | if len(section) > 0: 58 | if processList[-1] == "/": 59 | processList.append(f"/{section}") #avoid // when processing / 60 | else: 61 | processList.append(f"{processList[-1]}/{section}") 62 | 63 | ######################### 64 | 65 | ###Secong walking stage### 66 | 67 | #here we generate a fileSystemEntryData for each file and directory in the scope 68 | for level in processList: 69 | curLevelContents = list() 70 | elements = sorted(os.listdir(level)) #sort alphabetically while we're at it 71 | 72 | for element in elements: 73 | if not settings.FM_SHOW_HIDDEN and element[0] == ".": #filter hidden elements 74 | continue 75 | isOpen = False 76 | if tablevel < len(processList)-1: 77 | if element == processList[tablevel+1].split("/")[-1]: #a dir is open if it's the next on the list 78 | isOpen = True 79 | 80 | if os.path.isfile(f"{level}/{element}"): 81 | curLevelContents.append(fileSystemEntryData(enums.ELEMENT_TYPE_FILE, False, tablevel, element, f"{level}/{element}")) 82 | else: 83 | curLevelContents.append(fileSystemEntryData(enums.ELEMENT_TYPE_DIR, isOpen, tablevel, element, f"{level}/{element}")) 84 | 85 | #indentation level is based on how nested the element is 86 | tablevel += 1 87 | retList.append(curLevelContents) 88 | ########################## 89 | 90 | return retList 91 | 92 | def perr(msg): 93 | 94 | print(f"ERROR: {msg}") 95 | 96 | def dbgp(msg): 97 | 98 | if settings.DBG: 99 | print(f"DBG: {msg}") 100 | 101 | def checkFileType(filePath): 102 | 103 | magicValue = magic.from_file(filePath) 104 | 105 | for match in enums.TEXT_MAGIC_VALUES: 106 | if fnmatch(magicValue, match): 107 | return enums.DISPLAY_MODE_TEXT 108 | for match in enums.IMAGE_MAGIC_VALUES: 109 | if fnmatch(magicValue, match): 110 | return enums.DISPLAY_MODE_IMAGE 111 | for match in enums.AUDIO_MAGIC_VALUES: 112 | if fnmatch(magicValue, match): 113 | return enums.DISPLAY_MODE_AUDIO 114 | for match in enums.VIDEO_MAGIC_VALUES: 115 | if fnmatch(magicValue, match): 116 | return enums.DISPLAY_MODE_VIDEO 117 | else: 118 | return enums.DISPLAY_MODE_BINARY 119 | 120 | def escapePath(filePath): 121 | 122 | escapedPath = "" 123 | 124 | for c in filePath: 125 | if c in settings.ESCAPED_CHARS_LIST: 126 | escapedPath += "\\" + c 127 | else: 128 | escapedPath += c 129 | 130 | return escapedPath 131 | -------------------------------------------------------------------------------- /multiX/display/multixConfig.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from multixDefines import enums 4 | 5 | class settings: 6 | 7 | #Settings you may want to change 8 | DBG = False #Enable debug output 9 | TOPLEVEL_PATH = "/home/user/" #Substitute that for your home directory (or whatever you want as your toplevel dir) 10 | FM_SHOW_HIDDEN = False #Show hidden files in the gui 11 | 12 | #Commands to run in the terminal when opening files from GUI 13 | #%filepath will be replaced with the absolute file path 14 | 15 | TERMINAL_FOPEN_COMMANDS = { 16 | enums.DISPLAY_MODE_TEXT : "cat %filepath", 17 | enums.DISPLAY_MODE_IMAGE : "exiv2 pr %filepath", 18 | enums.DISPLAY_MODE_BINARY : "file %filepath", 19 | enums.DISPLAY_MODE_AUDIO : "ffprobe -hide_banner %filepath", 20 | enums.DISPLAY_MODE_VIDEO : "ffprobe -hide_banner %filepath", 21 | } 22 | 23 | #Settings you may want to leave at default 24 | DISPLAY_WINDOW_TITLE = "DISPLAY" 25 | SOCKET_PATH = "/tmp/multix.socket" 26 | ENTRY_NESTED_INDENTATION_PX = 10 27 | ENTRY_MAIN_BODY_PADDING_PX = 10 28 | DEFAULT_WINDOW_WIDTH = 640 29 | DEFAULT_WINDOW_HEIGHT = 800 30 | 31 | ESCAPED_CHARS_LIST = [ 32 | "\"", 33 | "\'", 34 | " ", 35 | "$", 36 | "|", 37 | "\\", 38 | "*", 39 | "&", 40 | ] 41 | 42 | class colors: 43 | 44 | COLOR_WINDOW_BACKGROUND = (0.0, 0.0, 0.0, 0.7) 45 | 46 | COLOR_BACKGROUND = (0.47, 0.47, 0.47, 1) 47 | 48 | COLOR_ENTRY_BACKGROUND = (0.17, 0.17, 0.17, 1) 49 | 50 | COLOR_ENTRY_MOUSE_HOVER = (1, 1, 1, 0.3) 51 | -------------------------------------------------------------------------------- /multiX/display/multixDefines.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from dataclasses import dataclass 4 | 5 | @dataclass 6 | class fileSystemEntryData: 7 | 8 | elementType: bool 9 | isOpen: bool 10 | tablevel: int 11 | label: str 12 | absolutePath: str 13 | 14 | 15 | class proto: 16 | 17 | """ 18 | Files 19 | """ 20 | 21 | COM_SOCKET = "/tmp/multix.pipe" 22 | 23 | """ 24 | Messaging protocol constants 25 | """ 26 | MSG_CWD = "CWD" 27 | MSG_RUN = "RUN" 28 | 29 | class enums: 30 | 31 | #enumeration kinda thing for displaying filesystem entries 32 | ELEMENT_TYPE_DIR = True 33 | ELEMENT_TYPE_FILE = False 34 | 35 | #enumeration kinda thing but for different display modes now 36 | DISPLAY_MODE_FM = 0 37 | DISPLAY_MODE_TEXT = 1 38 | DISPLAY_MODE_IMAGE = 2 39 | DISPLAY_MODE_AUDIO = 3 40 | DISPLAY_MODE_VIDEO = 4 41 | DISPLAY_MODE_BINARY = 5 42 | 43 | #lists of file extensions to match mediatypes against 44 | 45 | VIDEO_MAGIC_VALUES = [ 46 | "*AVI*", 47 | "*WebM*", 48 | "*MP4*", 49 | ] 50 | 51 | AUDIO_MAGIC_VALUES = [ 52 | "Audio file*", 53 | "FLAC audio bitstream data*", 54 | "Ogg data*", 55 | "MPEG ADTS, layer III*", 56 | ] 57 | 58 | IMAGE_MAGIC_VALUES = [ 59 | "PNG image data*", 60 | "JPEG image data*", 61 | ] 62 | 63 | TEXT_MAGIC_VALUES = [ 64 | "*ASCII text", 65 | "*Unicode text*", 66 | "*XML data*", 67 | ] 68 | -------------------------------------------------------------------------------- /multiX/display/multixGUI.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import gi 4 | 5 | gi.require_version("Gtk", "3.0") 6 | gi.require_version("Gdk", "3.0") 7 | from gi.repository import Gtk 8 | from gi.repository import Gdk 9 | from gi.repository import GObject 10 | import cairo 11 | import sys 12 | 13 | from multix import dbgp, decomposePath, decomposePath, checkFileType, escapePath 14 | from multixDefines import enums, fileSystemEntryData, proto 15 | from multixConfig import settings, colors 16 | 17 | class backButton(Gtk.EventBox): 18 | 19 | def __init__(self): 20 | super().__init__() 21 | self.connect("draw", self.onDraw) 22 | self.connect("enter-notify-event", self.onMouseEnter) 23 | self.connect("leave-notify-event", self.onMouseLeave) 24 | self.isMouseOver = False 25 | self.label = Gtk.Label() 26 | self.label.set_text("<-") 27 | self.add(self.label) 28 | self.set_size_request(30, 30) 29 | 30 | def onDraw(self, widget, cr): 31 | cr.set_source_rgba(*colors.COLOR_BACKGROUND) 32 | allocation = self.get_allocation() 33 | cr.rectangle(0, 0, allocation.width, allocation.height) 34 | cr.fill() 35 | cr.set_source_rgba(*colors.COLOR_ENTRY_BACKGROUND) 36 | cr.rectangle(1, 1, allocation.width-2, allocation.height-2) 37 | cr.fill() 38 | 39 | if self.isMouseOver: 40 | cr.set_source_rgba(*colors.COLOR_ENTRY_MOUSE_HOVER) 41 | cr.rectangle(self.get_margin_start(), 0, allocation.width, allocation.height) 42 | cr.fill() 43 | 44 | return False 45 | 46 | def onMouseEnter(self, event, userData): 47 | 48 | self.isMouseOver = True 49 | self.queue_draw() 50 | return False 51 | 52 | def onMouseLeave(self, event, userData): 53 | 54 | self.isMouseOver = False 55 | self.queue_draw() 56 | return False 57 | 58 | class fsEntryBox(Gtk.EventBox): 59 | 60 | def __init__(self, tablevel, label, elementType, absolutePath): 61 | super().__init__() 62 | 63 | if elementType == enums.ELEMENT_TYPE_DIR: 64 | self.label = Gtk.Label(label=f"/{label}") 65 | else: 66 | self.label = Gtk.Label(label=label) 67 | 68 | self.elementType = elementType 69 | 70 | self.add(self.label) 71 | self.set_margin_start(tablevel * settings.ENTRY_NESTED_INDENTATION_PX) 72 | self.absolutePath = absolutePath 73 | 74 | self.isMouseOver = False 75 | 76 | self.connect("draw", self.onDraw) 77 | self.connect("enter-notify-event", self.onMouseEnter) 78 | self.connect("leave-notify-event", self.onMouseLeave) 79 | 80 | def onDraw(self, widget, cr): 81 | cr.set_source_rgba(*colors.COLOR_BACKGROUND) 82 | allocation = self.get_allocation() 83 | cr.rectangle(self.get_margin_start(), 0, allocation.width, allocation.height) 84 | cr.fill() 85 | cr.set_source_rgba(*colors.COLOR_ENTRY_BACKGROUND) 86 | cr.rectangle(self.get_margin_start() + settings.ENTRY_MAIN_BODY_PADDING_PX, 1, allocation.width-(settings.ENTRY_MAIN_BODY_PADDING_PX+1), allocation.height-2) 87 | cr.fill() 88 | 89 | if self.isMouseOver: 90 | cr.set_source_rgba(*colors.COLOR_ENTRY_MOUSE_HOVER) 91 | cr.rectangle(self.get_margin_start(), 0, allocation.width, allocation.height) 92 | cr.fill() 93 | 94 | return False 95 | 96 | def onMouseEnter(self, event, userData): 97 | 98 | self.isMouseOver = True 99 | self.queue_draw() 100 | return False 101 | 102 | def onMouseLeave(self, event, userData): 103 | 104 | self.isMouseOver = False 105 | self.queue_draw() 106 | return False 107 | 108 | class displayWindow(Gtk.Window): 109 | 110 | def __init__(self, sendCallback): 111 | super().__init__(title = settings.DISPLAY_WINDOW_TITLE) 112 | 113 | self.set_default_size(settings.DEFAULT_WINDOW_WIDTH, settings.DEFAULT_WINDOW_HEIGHT) 114 | 115 | self.connect("draw", self.onDraw) 116 | 117 | screen = self.get_screen() 118 | visual = screen.get_rgba_visual() 119 | if visual and screen.is_composited(): 120 | self.set_visual(visual) 121 | self.set_app_paintable(True) 122 | 123 | self.vbox = Gtk.Box(orientation = "vertical", spacing=2) 124 | self.windowBox = Gtk.Box(orientation = "vertical") 125 | self.headerBox = Gtk.Box(orientation = "horizontal") 126 | self.windowBox.set_homogenous = False 127 | 128 | self.headerLabel = Gtk.Label() 129 | self.headerBackButton = backButton() 130 | 131 | self.headerBackButton.connect("button-press-event", self.onBackButtonPress) 132 | 133 | self.headerBox.pack_start(self.headerLabel, False, False, 0) 134 | self.headerBox.pack_end(self.headerBackButton, False, False, 0) 135 | 136 | self.headerBox.set_margin_bottom(10) 137 | 138 | self.scroll = Gtk.ScrolledWindow() 139 | 140 | self.windowBox.set_margin_start(10) 141 | self.windowBox.set_margin_end(10) 142 | self.windowBox.set_margin_top(20) 143 | self.windowBox.set_margin_bottom(20) 144 | 145 | self.activePath = settings.TOPLEVEL_PATH 146 | 147 | self.mode = enums.DISPLAY_MODE_FM 148 | 149 | dirStructure = decomposePath(self.activePath, settings.TOPLEVEL_PATH) 150 | 151 | self.serverSendCallback = sendCallback 152 | 153 | self._drawFm(dirStructure, 0) 154 | 155 | self.scroll.add(self.vbox) 156 | self.windowBox.add(self.headerBox) 157 | self.windowBox.add(self.scroll) 158 | 159 | self.windowBox.set_child_packing(self.scroll, True, True, 0, 0) 160 | 161 | self.add(self.windowBox) 162 | 163 | self.update() 164 | 165 | def onDraw(self, widget, cr): 166 | cr.set_source_rgba(*colors.COLOR_WINDOW_BACKGROUND) 167 | cr.set_operator(cairo.OPERATOR_SOURCE) 168 | cr.paint() 169 | cr.set_operator(cairo.OPERATOR_OVER) 170 | 171 | def _drawFm(self, structure, level): 172 | 173 | for element in structure[level]: 174 | self.vbox.add(fsEntryBox(element.tablevel, element.label, element.elementType, element.absolutePath)) 175 | if element.isOpen: 176 | self._drawFm(structure, level+1) 177 | 178 | def update(self): 179 | 180 | for item in self.vbox.get_children(): 181 | item.destroy() 182 | 183 | if self.mode == enums.DISPLAY_MODE_FM: 184 | 185 | dirStructure = decomposePath(self.activePath, settings.TOPLEVEL_PATH) 186 | if dirStructure: 187 | self.headerLabel.set_text(self.activePath) 188 | self._drawFm(dirStructure, 0) 189 | for item in self.vbox.get_children(): 190 | item.connect("button-press-event", self.onEntryClick) 191 | 192 | elif self.mode == enums.DISPLAY_MODE_TEXT: 193 | 194 | txtBuf = "" 195 | try: 196 | with open(self.activePath, "r") as tgtFile: 197 | txtBuf = tgtFile.read() 198 | except Exception as e: 199 | txtBuf = f"Failed reading the file : {e}" 200 | 201 | self.headerLabel.set_text(self.activePath) 202 | txtView = Gtk.TextView() 203 | self.vbox.add(txtView) 204 | txtView.get_buffer().set_text(txtBuf, len(txtBuf)) 205 | 206 | elif self.mode == enums.DISPLAY_MODE_IMAGE: 207 | 208 | self.headerLabel.set_text(self.activePath) 209 | im = Gtk.Image() 210 | self.vbox.add(im) 211 | im.set_from_file(self.activePath) 212 | 213 | self.show_all() 214 | 215 | return True 216 | 217 | def onEntryClick(self, widget, event): 218 | 219 | dbgp(f"User clicked on: {widget.label.get_text()}") 220 | 221 | self.activePath = widget.absolutePath 222 | if widget.elementType == enums.ELEMENT_TYPE_DIR: 223 | self.update() 224 | self.serverSendCallback(f"{proto.MSG_CWD} {self.activePath}") 225 | else: 226 | self.mode = checkFileType(widget.absolutePath) 227 | dbgp(f"Tried opening a file: {widget.absolutePath}") 228 | self.update() 229 | self.serverSendCallback(f"{proto.MSG_RUN} {settings.TERMINAL_FOPEN_COMMANDS[self.mode].replace('%filepath', escapePath(self.activePath))}") 230 | 231 | return False 232 | 233 | def onBackButtonPress(self, widget, event): 234 | 235 | dbgp("User clicked the back button") 236 | 237 | if self.mode != enums.DISPLAY_MODE_FM: 238 | self.mode = enums.DISPLAY_MODE_FM 239 | 240 | self.activePath = self.activePath[:self.activePath.rindex("/")] #move one slash back 241 | 242 | self.update() 243 | self.serverSendCallback(f"{proto.MSG_CWD} {self.activePath}") 244 | -------------------------------------------------------------------------------- /multiX/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | python3 /home/remi/.local/projects/hacknetGtk/multiX/display/daemon.py & 3 | cd augmentation 4 | sleep 1 5 | make run 6 | -------------------------------------------------------------------------------- /multiX/screenshots/fmview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/project-kaat/hacknetGTK/475d5f747343a6e6f9ff65cc7891cef4286c3b05/multiX/screenshots/fmview.png -------------------------------------------------------------------------------- /multiX/screenshots/original_fmview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/project-kaat/hacknetGTK/475d5f747343a6e6f9ff65cc7891cef4286c3b05/multiX/screenshots/original_fmview.png -------------------------------------------------------------------------------- /multiX/screenshots/original_textview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/project-kaat/hacknetGTK/475d5f747343a6e6f9ff65cc7891cef4286c3b05/multiX/screenshots/original_textview.png -------------------------------------------------------------------------------- /multiX/screenshots/textview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/project-kaat/hacknetGTK/475d5f747343a6e6f9ff65cc7891cef4286c3b05/multiX/screenshots/textview.png -------------------------------------------------------------------------------- /ramVis/Makefile: -------------------------------------------------------------------------------- 1 | LIBS := `pkg-config --cflags --libs gtk+-2.0` 2 | build: 3 | gcc ramvis.c -o ramvis $(LIBS) 4 | clean: 5 | rm ramvis 6 | -------------------------------------------------------------------------------- /ramVis/README.md: -------------------------------------------------------------------------------- 1 | # Ram visualization window 2 | 3 | This is a remake of "RAM" from Hacknet. There are some configuration values available in the source file. 4 | Build it with `make build`. 5 | 6 | The window will show a progressbar of occupied and active system memory. If text is enabled, two labels will be rendered: one indicating ram usage, the other indicating the number of active processes. 7 | 8 | **"active" and "occupied" memory:** 9 | 10 | Occupied refers to memory that's reserved in any way by the OS. It includes cached memory too. 11 | 12 | Active memory is a subset of occupied memory. It is the memory that is actively used by some application. 13 | 14 | ## Screenshots 15 | 16 | ### Original 17 | 18 | ![original screenshot from the game](./screenshots/original.png) 19 | 20 | ### Project HacknetGTK 21 | 22 | ![screenshot of ramVis](./screenshots/ramvis.png) 23 | -------------------------------------------------------------------------------- /ramVis/ramvis: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/project-kaat/hacknetGTK/475d5f747343a6e6f9ff65cc7891cef4286c3b05/ramVis/ramvis -------------------------------------------------------------------------------- /ramVis/ramvis.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | typedef struct _meminfo { 11 | uint32_t totalKb; 12 | uint32_t occupiedKb; 13 | uint32_t activeKb; 14 | } meminfo_t; 15 | 16 | typedef struct _gui_color_t { 17 | float red; 18 | float green; 19 | float blue; 20 | float alpha; 21 | } _gui_color_t; 22 | 23 | //-=-=-=-=CONFIGURATION=-=-=-=- 24 | 25 | const uint8_t POLLING_INTERVAL = 2; //seconds between each read of system info 26 | const uint16_t BAR_HEIGHT = 20; //default RAM visualization bar height in px 27 | const uint16_t BAR_WIDTH = 300; //default RAM visualization bar width in px 28 | const bool SHOW_TEXT = true; //whether to print the text information on the bar 29 | const bool ON_TOP = true; //whether to keep window always on top 30 | 31 | const char * WINDOW_TITLE = "RAM"; 32 | 33 | const _gui_color_t COLOR_BACKGROUND = { //window background 34 | .red = 0, 35 | .green = 0, 36 | .blue = 0, 37 | .alpha = 1 38 | }; 39 | const _gui_color_t COLOR_OCCUPIED = { //occupied memory color 40 | .red = 0.415, 41 | .green = 0.384, 42 | .blue = 0.384, 43 | .alpha = 1 44 | }; 45 | const _gui_color_t COLOR_ACTIVE = { //active memory color 46 | .red = 0.364, 47 | .green = 0.337, 48 | .blue = 0.337, 49 | .alpha = 1 50 | }; 51 | 52 | //-=-=-=-=END CONFIGURATION=-=-=- 53 | 54 | #define MINFO_READ_LINE_SIZE 128 55 | #define PS_NUMBER_READ_SIZE 16 56 | #define RAMTEXT_SIZE 64 57 | #define PSTEXT_SIZE 5 58 | 59 | static meminfo_t CURRENT_VALUES = {0}; 60 | static meminfo_t CURRENT_PERCENTAGES = {0}; 61 | static uint16_t CURRENT_PROCS = 0; 62 | GtkWidget * gtkWin = NULL; 63 | GtkWidget * ramLabel = NULL; 64 | GtkWidget * psLabel = NULL; 65 | 66 | bool pollMeminfo(meminfo_t * values) { 67 | 68 | FILE * minfo = fopen("/proc/meminfo", "r"); 69 | if (minfo == NULL) { 70 | printf("Can't open /proc/meminfo for reading\n"); 71 | return false; 72 | } 73 | 74 | bool parsingFinished = false; 75 | bool totalParsed, availableParsed, activeParsed; 76 | totalParsed = availableParsed = activeParsed = 0; 77 | 78 | char minfoBuf[MINFO_READ_LINE_SIZE] = {0}; 79 | while (parsingFinished != true && fgets(minfoBuf, MINFO_READ_LINE_SIZE, minfo) != NULL) { 80 | 81 | //parse total 82 | if (!totalParsed && (strncmp(minfoBuf, "MemTotal", 8) == 0)) { 83 | 84 | sscanf(minfoBuf, "MemTotal: %u", &values->totalKb); 85 | totalParsed = true; 86 | } 87 | 88 | //parse available 89 | else if (!availableParsed && (strncmp(minfoBuf, "MemAvailable", 12) == 0)) { 90 | 91 | uint32_t tmpAvailable; 92 | sscanf(minfoBuf, "MemAvailable: %u", &tmpAvailable); 93 | values->occupiedKb = values->totalKb - tmpAvailable; 94 | availableParsed = true; 95 | } 96 | 97 | //parse active 98 | else if (!activeParsed && (strncmp(minfoBuf, "Active:", 7) == 0)) { 99 | 100 | sscanf(minfoBuf, "Active: %u", &values->activeKb); 101 | activeParsed = true; 102 | } 103 | 104 | parsingFinished = totalParsed & availableParsed & activeParsed; 105 | 106 | } 107 | 108 | if (!parsingFinished) { 109 | printf("Falied to parse /proc/meminfo\n"); 110 | return false; 111 | } 112 | 113 | fclose(minfo); 114 | return true; 115 | 116 | } 117 | 118 | void minfoPercConvert(meminfo_t * src, meminfo_t * dst) { 119 | 120 | dst->totalKb = 100; 121 | dst->occupiedKb = (uint32_t)(src->occupiedKb * 100 / src->totalKb); 122 | dst->activeKb = (uint32_t)(src->activeKb * 100 / src->totalKb); 123 | 124 | } 125 | 126 | uint16_t pollProcs() { 127 | 128 | uint16_t retval; 129 | 130 | char buf[PS_NUMBER_READ_SIZE] = {0}; 131 | FILE * process = popen("ps -aux | wc -l", "r"); 132 | if (fgets(buf, PS_NUMBER_READ_SIZE, process) == NULL) { 133 | printf("Can't query number of system processes\n"); 134 | pclose(process); 135 | return 0; 136 | } 137 | retval = atoi(buf); 138 | 139 | pclose(process); 140 | 141 | return retval; 142 | 143 | } 144 | 145 | void printMeminfo(meminfo_t * meminfo) { 146 | 147 | printf("Total memory: %u\n", meminfo->totalKb); 148 | printf("Memory occupied: %u\n", meminfo->occupiedKb); 149 | printf("Memory active: %u\n", meminfo->activeKb); 150 | 151 | } 152 | 153 | bool update() { 154 | 155 | if (!pollMeminfo(&CURRENT_VALUES)) { 156 | return false; 157 | } 158 | 159 | if (SHOW_TEXT) { 160 | uint16_t poll = pollProcs(); 161 | if (poll == 0) { 162 | return false; 163 | } 164 | CURRENT_PROCS = poll; 165 | } 166 | 167 | minfoPercConvert(&CURRENT_VALUES, &CURRENT_PERCENTAGES); 168 | 169 | return true; 170 | } 171 | 172 | void drawingSetBackground(cairo_t * cr) { 173 | 174 | cairo_set_source_rgba(cr, COLOR_BACKGROUND.red, COLOR_BACKGROUND.green, COLOR_BACKGROUND.blue, COLOR_BACKGROUND.alpha); 175 | cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); 176 | cairo_paint(cr); 177 | 178 | } 179 | 180 | void drawingProgressBarPart(cairo_t * cr, uint16_t height, uint16_t width, const _gui_color_t * color) { 181 | 182 | cairo_set_source_rgba(cr, color->red, color->green, color->blue, color->alpha); 183 | cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); 184 | cairo_rectangle(cr, 0, 0, width, height); 185 | cairo_fill(cr); 186 | } 187 | 188 | static gboolean onRedraw(GtkWidget *widget, GdkEventExpose *event, gpointer userdata) { 189 | 190 | cairo_t * cr = gdk_cairo_create(widget->window); 191 | 192 | int32_t width = 0; 193 | int32_t height = 0; 194 | 195 | gtk_window_get_size(GTK_WINDOW(widget), &width, &height); 196 | 197 | if (width == 0 || height == 0) { 198 | width = BAR_WIDTH; 199 | height = BAR_HEIGHT; 200 | } 201 | 202 | uint16_t occupiedWidth = width * CURRENT_PERCENTAGES.occupiedKb / 100; 203 | uint16_t activeWidth = width * CURRENT_PERCENTAGES.activeKb / 100; 204 | 205 | drawingSetBackground(cr); 206 | drawingProgressBarPart(cr, height, occupiedWidth, &COLOR_OCCUPIED); 207 | drawingProgressBarPart(cr, height, activeWidth, &COLOR_ACTIVE); 208 | 209 | cairo_destroy(cr); 210 | 211 | return FALSE; 212 | } 213 | 214 | static void onScreenChanged(GtkWidget * widget, GdkScreen * old_screen, gpointer userdata) { 215 | 216 | GdkScreen * screen = gtk_widget_get_screen(widget); 217 | GdkColormap * cmap = gdk_screen_get_rgba_colormap(screen); 218 | 219 | gtk_widget_set_colormap(widget, cmap); 220 | 221 | } 222 | 223 | static gboolean onTimer(GtkWidget * widget) { 224 | 225 | if (!update()) { 226 | printf("Polling cycle failed\n"); 227 | gtk_main_quit(); 228 | } 229 | 230 | gtk_widget_queue_draw(widget); 231 | 232 | return TRUE; 233 | } 234 | 235 | static gboolean updateRamLabel(GtkWidget * label) { 236 | 237 | char ramText[RAMTEXT_SIZE] = {0}; 238 | 239 | snprintf(ramText, RAMTEXT_SIZE, "USED RAM: %uMb/%uMb", (uint32_t)CURRENT_VALUES.occupiedKb/1024, (uint32_t)CURRENT_VALUES.totalKb/1024); 240 | 241 | gtk_label_set_text(GTK_LABEL(ramLabel), ramText); 242 | 243 | return TRUE; 244 | } 245 | 246 | static gboolean updatePsLabel(GtkWidget * label) { 247 | 248 | char psText[PSTEXT_SIZE] = {0}; 249 | 250 | snprintf(psText, PSTEXT_SIZE, "%u", CURRENT_PROCS); 251 | 252 | gtk_label_set_text(GTK_LABEL(psLabel), psText); 253 | 254 | return TRUE; 255 | } 256 | 257 | bool initWindow() { 258 | 259 | gtkWin = gtk_window_new(GTK_WINDOW_TOPLEVEL); 260 | 261 | gtk_window_set_position(GTK_WINDOW(gtkWin), GTK_WIN_POS_CENTER); 262 | gtk_window_set_default_size(GTK_WINDOW(gtkWin), BAR_WIDTH, BAR_HEIGHT); 263 | gtk_window_set_title(GTK_WINDOW(gtkWin), WINDOW_TITLE); 264 | 265 | gtk_widget_set_app_paintable(gtkWin, TRUE); 266 | 267 | if (ON_TOP) { 268 | gtk_window_set_keep_above(GTK_WINDOW(gtkWin), TRUE); 269 | } 270 | 271 | if (SHOW_TEXT) { 272 | GtkWidget * usedram; 273 | GtkWidget * ps; 274 | GtkWidget * textHBox; 275 | 276 | ramLabel = gtk_label_new(NULL); 277 | psLabel = gtk_label_new(NULL); 278 | 279 | textHBox = gtk_hbox_new(FALSE, 0); 280 | 281 | gtk_box_pack_start(GTK_BOX(textHBox), GTK_WIDGET(ramLabel), FALSE, FALSE, 0); 282 | gtk_box_pack_end(GTK_BOX(textHBox), GTK_WIDGET(psLabel), FALSE, FALSE, 0); 283 | 284 | gtk_container_add(GTK_CONTAINER(gtkWin), textHBox); 285 | 286 | } 287 | 288 | g_signal_connect(G_OBJECT(gtkWin), "delete-event", gtk_main_quit, NULL); 289 | g_signal_connect(G_OBJECT(gtkWin), "expose-event", G_CALLBACK(onRedraw), NULL); 290 | g_signal_connect(G_OBJECT(gtkWin), "screen-changed", G_CALLBACK(onScreenChanged), NULL); 291 | 292 | //don't wanna change any widgets outside of the main event loop 293 | g_timeout_add(POLLING_INTERVAL * 1000, (GSourceFunc) onTimer, (gpointer) gtkWin); 294 | if (SHOW_TEXT) { 295 | g_timeout_add(POLLING_INTERVAL * 1000, (GSourceFunc) updateRamLabel, (gpointer) ramLabel); 296 | g_timeout_add(POLLING_INTERVAL * 1000, (GSourceFunc) updatePsLabel, (gpointer) psLabel); 297 | } 298 | 299 | onScreenChanged(gtkWin, NULL, NULL); 300 | onTimer(gtkWin); 301 | updateRamLabel(ramLabel); 302 | updatePsLabel(psLabel); 303 | 304 | return true; 305 | } 306 | 307 | int main(int argc, char **argv) { 308 | 309 | gtk_init(&argc, &argv); 310 | 311 | if (!initWindow()) { 312 | printf("Could not initialize GUI\n"); 313 | return 1; 314 | } 315 | 316 | gtk_widget_show_all(gtkWin); 317 | 318 | gtk_main(); 319 | 320 | return 0; 321 | } 322 | 323 | -------------------------------------------------------------------------------- /ramVis/screenshots/original.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/project-kaat/hacknetGTK/475d5f747343a6e6f9ff65cc7891cef4286c3b05/ramVis/screenshots/original.png -------------------------------------------------------------------------------- /ramVis/screenshots/ramvis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/project-kaat/hacknetGTK/475d5f747343a6e6f9ff65cc7891cef4286c3b05/ramVis/screenshots/ramvis.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | python-magic 2 | pygobject 3 | pycairo 4 | -------------------------------------------------------------------------------- /screenshots/unixporn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/project-kaat/hacknetGTK/475d5f747343a6e6f9ff65cc7891cef4286c3b05/screenshots/unixporn.png -------------------------------------------------------------------------------- /traceAnim/Makefile: -------------------------------------------------------------------------------- 1 | LIBS := `pkg-config --cflags --libs gtk+-2.0` 2 | build: 3 | gcc traceAnim.c -o traceAnim $(LIBS) 4 | clean: 5 | rm traceAnim 6 | -------------------------------------------------------------------------------- /traceAnim/README.md: -------------------------------------------------------------------------------- 1 | # Trace animation 2 | 3 | This is a remake of the famous Hacknet trace animation. Done with gtk2.0 and C. There are some commented configuration values in the source file. 4 | Build it with `make build`. 5 | 6 | You can configure the colors, fullscreen window geometry, timings, and line heights. 7 | There is also a feature that allows you to run an arbitrary command using `int system(const char *command);`, when the animation is finished. (would make a nice shutdown animation, for example) 8 | 9 | ## Screenshots 10 | 11 | ### Original 12 | 13 | ![original animation screenshot from youtube.com](https://i.ytimg.com/vi/XdPmJxJHf98/maxresdefault.jpg) 14 | ### Project HacknetGTK (excuse the recording quality) 15 | 16 | ![recorded screencap of traceAnim](https://user-images.githubusercontent.com/95129982/156930166-b70022a6-0a41-497a-a479-8d281c675f6d.mp4) 17 | 18 | -------------------------------------------------------------------------------- /traceAnim/screenshots/traceanim.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/project-kaat/hacknetGTK/475d5f747343a6e6f9ff65cc7891cef4286c3b05/traceAnim/screenshots/traceanim.mp4 -------------------------------------------------------------------------------- /traceAnim/traceAnim: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/project-kaat/hacknetGTK/475d5f747343a6e6f9ff65cc7891cef4286c3b05/traceAnim/traceAnim -------------------------------------------------------------------------------- /traceAnim/traceAnim.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include //usleep 4 | #include 5 | #include 6 | #include //sleep, system 7 | #include 8 | #include 9 | 10 | typedef struct _anim_color_t { 11 | float red; 12 | float green; 13 | float blue; 14 | float alpha; 15 | } anim_color_t; 16 | 17 | //-=-=-=-=CONFIGURATION=-=-=-=- 18 | 19 | const uint16_t WINDOW_WIDTH = 0; //set to 0 for auto-detection on the primary monitor 20 | const uint16_t WINDOW_HEIGHT = 0; //set to 0 for auto-detection on the primary monitor 21 | const uint8_t SCANLINES_SPACING = 2; //px between the alternating lines 22 | const uint8_t SEC_LINE_SPACING = 2; //px betweeen the secondary and the primary bottom lines 23 | const uint8_t BAR_HEIGHT = 1; //line height in px 24 | const uint8_t TIME_TO_REACH = 10; //animation should reach the bottom of the screen in N seconds 25 | const uint8_t BOTTOM_STAY_TIME = 3; //animation will stay on the screen for N seconds once finished 26 | const char * TERMINATION_COMMAND = NULL; //this command can be run in your default system shell after the animation is finished. Use (NULL) to disable this feature completely 27 | 28 | //colors are named after the original animation colors (see screenshots) 29 | const anim_color_t COLOR_RED = { //primary scanline color 30 | .red = 0.701, 31 | .green = 0, 32 | .blue = 0, 33 | .alpha = 0.7 34 | }; 35 | 36 | const anim_color_t COLOR_ALTRED = { //secondary scanline color 37 | .red = 0.301, 38 | .green = 0, 39 | .blue = 0, 40 | .alpha = 0.7 41 | }; 42 | 43 | const anim_color_t COLOR_WHITE = { //primary (lower) bottom line color 44 | .red = 1, 45 | .green = 1, 46 | .blue = 1, 47 | .alpha = 1 48 | }; 49 | const anim_color_t COLOR_GRAY = { //secondary (upper) bottom line color 50 | .red = 0.745, 51 | .green = 0.745, 52 | .blue = 0.745, 53 | .alpha = 1 54 | }; 55 | 56 | const anim_color_t COLOR_BACKGROUND = { //window background color 57 | .red = 0, 58 | .green = 0, 59 | .blue = 0, 60 | .alpha = 0 61 | }; 62 | 63 | //-=-=-=-=END CONFIGURATION=-=-=-=- 64 | 65 | GtkWidget * gtkWin; 66 | uint16_t _WINDOW_WIDTH; 67 | uint16_t _WINDOW_HEIGHT; 68 | 69 | static void onScreenChanged(GtkWidget * widget, GdkScreen * old_screen, gpointer userdata) { 70 | 71 | GdkScreen * screen = gtk_widget_get_screen(widget); 72 | GdkColormap * cmap = gdk_screen_get_rgba_colormap(screen); 73 | 74 | if (!cmap) { 75 | printf("RGBA colormap not found. Quitting\n"); 76 | gtk_main_quit(); 77 | } 78 | 79 | gtk_widget_set_colormap(widget, cmap); 80 | } 81 | 82 | static gboolean onExposed(GtkWidget *widget, GdkEventExpose * event, gpointer userdata) { 83 | 84 | cairo_t *cr = gdk_cairo_create(widget->window); 85 | 86 | drawingClear(cr); 87 | 88 | cairo_destroy(cr); 89 | return FALSE; 90 | } 91 | 92 | void drawingHorizontalLine(cairo_t * cr, uint16_t yaxis, uint16_t height, uint16_t width, const anim_color_t * color) { 93 | 94 | cairo_set_source_rgba(cr, color->red, color->green, color->blue, color->alpha); 95 | cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); 96 | cairo_rectangle(cr, 0, yaxis, width, height); 97 | cairo_fill(cr); 98 | } 99 | 100 | void drawingClear(cairo_t * cr) { 101 | 102 | cairo_set_source_rgba(cr, COLOR_BACKGROUND.red, COLOR_BACKGROUND.green, COLOR_BACKGROUND.blue, COLOR_BACKGROUND.alpha); 103 | cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); 104 | cairo_paint(cr); 105 | 106 | } 107 | 108 | bool initWindow() { 109 | 110 | gtkWin = gtk_window_new(GTK_WINDOW_TOPLEVEL); 111 | 112 | if (WINDOW_WIDTH == 0 || WINDOW_HEIGHT == 0) { 113 | _WINDOW_WIDTH = gdk_screen_width(); 114 | _WINDOW_HEIGHT = gdk_screen_height(); 115 | } 116 | else { 117 | _WINDOW_WIDTH = WINDOW_WIDTH; 118 | _WINDOW_HEIGHT = WINDOW_HEIGHT; 119 | } 120 | gtk_window_set_position(GTK_WINDOW(gtkWin), GTK_WIN_POS_CENTER); 121 | gtk_window_set_default_size(GTK_WINDOW(gtkWin), _WINDOW_WIDTH, _WINDOW_HEIGHT); 122 | gtk_window_set_title(GTK_WINDOW(gtkWin), "TestTitle"); 123 | 124 | gtk_widget_set_app_paintable(gtkWin, TRUE); 125 | gtk_window_fullscreen(GTK_WINDOW(gtkWin)); 126 | 127 | g_signal_connect(G_OBJECT(gtkWin), "delete-event", gtk_main_quit, NULL); 128 | g_signal_connect(G_OBJECT(gtkWin), "screen-changed", G_CALLBACK(onScreenChanged), NULL); 129 | g_signal_connect(G_OBJECT(gtkWin), "expose-event", G_CALLBACK(onExposed), NULL); 130 | 131 | 132 | onScreenChanged(gtkWin, NULL, NULL); 133 | 134 | return true; 135 | } 136 | 137 | 138 | int main(int argc, char **argv) { 139 | 140 | gtk_init(&argc, &argv); 141 | 142 | if (!initWindow()) { 143 | printf("Could not initialize a gtk window\n"); 144 | return 1; 145 | } 146 | 147 | gtk_widget_show_all(gtkWin); 148 | 149 | /*calculate required time between each yaxis decrement*/ 150 | uint64_t timestep = (uint64_t) (TIME_TO_REACH * 1000) / (_WINDOW_HEIGHT / BAR_HEIGHT); 151 | timestep *= 1000; //convert to microseconds 152 | 153 | for (uint16_t i = 0; i < _WINDOW_HEIGHT; i++) { 154 | 155 | cairo_t *cr = gdk_cairo_create(gtkWin->window); 156 | 157 | drawingClear(cr); 158 | drawingHorizontalLine(cr, i, BAR_HEIGHT, _WINDOW_WIDTH, &COLOR_WHITE); 159 | if (i > 1) { 160 | drawingHorizontalLine(cr, 0, i, _WINDOW_WIDTH, &COLOR_RED); 161 | } 162 | if (i >= SEC_LINE_SPACING) { 163 | drawingHorizontalLine(cr, i-SEC_LINE_SPACING, BAR_HEIGHT, _WINDOW_WIDTH, &COLOR_GRAY); 164 | } 165 | if (i >= SCANLINES_SPACING) { 166 | for (uint16_t alti = SCANLINES_SPACING; alti < i - SEC_LINE_SPACING; alti += SCANLINES_SPACING) { 167 | drawingHorizontalLine(cr, alti, BAR_HEIGHT, _WINDOW_WIDTH, &COLOR_ALTRED); 168 | } 169 | } 170 | 171 | cairo_destroy(cr); 172 | 173 | gtk_main_iteration_do(FALSE); 174 | usleep(timestep); 175 | } 176 | 177 | if (TERMINATION_COMMAND != NULL) { 178 | system(TERMINATION_COMMAND); 179 | } 180 | 181 | if (BOTTOM_STAY_TIME) { 182 | sleep(BOTTOM_STAY_TIME); 183 | } 184 | 185 | 186 | 187 | return 0; 188 | } 189 | 190 | --------------------------------------------------------------------------------