Skip to content

Monday, 4 March 2024

myls

ls 是一个常用的命令行工具,用于列出指定目录中的文件和子目录。ls 的基本语法是:

1
ls [选项] [文件或目录]

以下是一些常用的 ls 命令选项:

  1. -l:以长格式显示文件信息,包括文件类型、权限、所有者、组、大小、修改时间等。

    1
    ls -l
  2. -a:显示所有文件,包括隐藏文件(以点开头的文件)。

    1
    ls -a
  3. -r:与 -l 逆序输出文件信息。

    1
    ls -r
  4. -R:递归显示子目录中的文件。

    1
    ls -R
  5. -t:按修改时间排序,最新修改的文件显示在前面。

    1
    ls -lt
  6. -S:按文件大小排序,最大的文件显示在前面。

    1
    ls -lS
  7. -i:显示文件的 inode 号码。

    1
    ls -i

思路

1.-i -a -R -r -i -s -l为命令行参数,我们首先需要解析命令行参数。

2.我们需要对以上参数进行了解,并进行构思一个框架以便后续对代码的维护与可读性。

3.我觉得可以将以上参数分为三类:1.-a 为确定需要显示的文件多少,例如-a包含 . 隐藏文件。2.-s -t 排序参数,如果含有该参数,则就需要对文件显示进行排序。3.-i -l 为输出参数,即在输出的时候进行判断是否需要输出对应的信息.

4.ls 还可以指定文件进行查看,我们还需要对不同的文件或目录进行操作.

相关函数

  • getopt,getcwd

  • opendir,readdir,closedir

  • snprintf

  • stat,lstat

主函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
enum order {
a, s, t, r, I, l, R
};//枚举命令行参数。提高代码的可读性.

int main(int argc, char *argv[])
{
int find = 0;//用来接收getopt的返回值
int orders[8] = {0};//存储对应参数的信息
for (int i = 1; i < argc; i++)
{
if (argv[i][0] == '-')
{
while ((find = getopt(argc, argv, "aIlrstR")) != -1)//解析命令行参数
{
switch (find)//将对应的参数信息残赋值为1.
{
case 'a':
orders[a] = 1;
break;
case 'r':
orders[r] = 1;
break;
case 's':
orders[s] = 1;
break;
case 't':
orders[t] = 1;
break;
case 'l':
orders[l] = 1;
break;
case 'R':
orders[R] = 1;
break;
case 'I':
orders[I] = 1;
break;
default:
fprintf(stderr, "Invalid option\n");
exit(EXIT_FAILURE);
}
}
}
}

int countfile = 0;
for (int i = 1; i < argc; i++)//获取文件的数量.
{
if (argv[i][0] != '-')
{
countfile++;
}
}

if (countfile == 0)
{
char current_path[MAX_PATH];//存储当前的工作路径
if ((getcwd(current_path, MAX_PATH)) != NULL)
{
if (orders[R] == 0)
{
do_open(current_path, orders);
}
else
{
do_open_R(current_path, orders);
}
}
else
{
perror("getcwd");
exit(EXIT_FAILURE);
}
}

for (int i = 1; i < argc; i++)
{
if (argv[i][0] != '-')
{
if (countfile > 1)
{
printf("%s\n", argv[i]);
}
if (orders[R] == 0)
{
do_open(argv[i], orders);
}
else
{
do_open_R(argv[i],orders);
}
}
}
}

了解了ls的基本语法与功能后,我们就需要进行实现.

那么就首先需要解析命令行参数.获取我们本次操作需要进行的工作.

getopt

该函数专门用来解析命令行参数.

1
int getopt(int argc, char * const argv[], const char *optstring);

参数说明

  • argc:通常由 main 函数直接传入,表示参数的数量

  • argv:通常也由 main 函数直接传入,表示参数的字符串变量数组

  • optstring:一个包含正确的参数选项字符串,用于参数的解析。例如 “abc:”,其中 -a,-b 就表示两

    个普通选项,-c 表示一个必须有参数的选项,因为它后面有一个冒号.

这里命令行参数已经解析,那么我们接下来就要进行对要查看的文件或目录数量进行统计.

如果数量大于2,就需要在进行操作前输出本次查看的文件或目录路径.

getcwd

1
2
#include<unistd.h>
char *getcwd(char *buf,size_t size);

介绍:
参数说明:getcwd()会将当前工作目录的绝对路径复制到参数buffer所指的内存空间中,参数size为buf的空间大小。

如果未传入文件或目录,就需要对当前路径下的文件进行操作.所以需要用到该函数.


现在已经获取到需要执行的参数和文件,接下来就需要进行参数操作.

所有参数中最难的就是递归部分,所以我们单独为-R封装一个函数来进行递归操作.

do_open

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
struct fileinfo//由于-l需要输出文件的详细信息,所以需要结构体来存储信息
{
unsigned int i_node;
char permission[16];
short owner;
short group;
off_t size;
time_t c_ctime;
time_t m_ctime;
nlink_t link_num;
char name[MAX_PATH];
mode_t mode;
blkcnt_t block;
};

void do_open(char *path, int orders[])
{
int count = 0;
struct fileinfo infos[MAX_FILE];//我们的-s -t -r参数需要对文件进行排序,那么我们就需要数组来存储文件相关信息。
if (orders[a])
{
ls_a(path, infos, &count);//ls-用来打开文件以及存储文件名称
}
if (orders[a] == 0)
{
only_ls(path, infos, &count);
}
if (orders[t])
{
qsort(infos, count, sizeof(struct fileinfo), cmp_time);//对存储的信息进行相关的排序.t的优先级高于s
}
if ((orders[s] == 1) && (orders[t] == 0))
{
qsort(infos, count, sizeof(struct fileinfo), cmp_size);
}
if ((orders[s] == 0) && orders[t] == 0)
{
qsort(infos, count, sizeof(struct fileinfo), cmp_name);//按名称排序,使文字命名文件在前
}
show_orders(infos, count, orders);//最后进行输出.
}

既然获取到了参数与文件,所以我们的do_open就需要接受这两类参数.

我们在do_open中进行文件打开,排序参数,以及展示.

count参数用于计数文件个数,便于输出结果.

do_open_R

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
void do_open_R(char *path, int orders[])
{
int count = 0;
struct fileinfo *infos = (struct fileinfo *)malloc(sizeof(struct fileinfo) * MAX_RFILE);
if (infos == NULL)
{
perror("malloc");
exit(EXIT_FAILURE);
}
if (orders[a])
{
ls_a(path, infos, &count);
}
if (orders[a] == 0)
{
only_ls(path, infos, &count);
}
if (orders[t])
{
qsort(infos, count, sizeof(struct fileinfo), cmp_time);
}
if ((orders[s] == 1) && (orders[t] == 0))
{
qsort(infos, count, sizeof(struct fileinfo), cmp_size);
}
if ((orders[s] == 0) && orders[t] == 0)
{
qsort(infos, count, sizeof(struct fileinfo), cmp_name);
}
show_orders(infos, count, orders);
for (int i = 0; i < count; i++)
{
if (S_ISDIR(infos[i].mode) && strcmp(infos[i].name, ".") != 0 && strcmp(infos[i].name, "..") != 0)
{
char sub_path[MAX_PATH];
snprintf(sub_path, sizeof(sub_path), "%s/%s", path, infos[i].name);
struct stat flag;
if (lstat(sub_path, &flag) == -1)
{
fprintf(stderr, "Permission denied or error reading: %s\n", sub_path);
continue;
}
if(S_ISLNK(flag.st_mode))
{
continue;
}
do_open_R(sub_path,orders);
}
}
free(infos);
}

由于-R参数需要递归,所以采取堆上开辟空间进行存储

这里多了一个判断,如果存储的路径对应的仍为目录,则需要继续进行简单的do_open操作。

所以S_ISDIR用来判断是否是目录,如果是则进行递归操作。

S_ISLNK用来判断文件是否为符号链接,如果不加该判断,会在对 / 目录下/dev/fd中的符号链接进行叠加路径而出现错误.

ls_a

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
void ls_a(char *path, struct fileinfo *infos, int *count)
{
DIR *dir;//接收opendir的返回值.
struct dirent *dirp;//readdir的返回值
if ((dir = opendir(path)) == NULL)
{
printf("%s\n", path);
return ;
}
*count = 0;
struct stat st;
while ((dirp = readdir(dir)) != NULL)
{
char current_path[MAX_PATH];
snprintf(current_path, sizeof(current_path), "%s/%s", path, dirp->d_name);

if ((stat(current_path, &st)) != -1)//存储文件的相关信息。
{

strncpy(infos[*count].name, dirp->d_name, sizeof(infos[*count].name) - 1);
infos[*count].name[sizeof(infos[*count].name) - 1] = '\0'; // Null-terminate the string
infos[*count].group = st.st_gid;
infos[*count].owner = st.st_uid;
infos[*count].mode = st.st_mode;
infos[*count].c_ctime = st.st_ctime;
infos[*count].size = st.st_size;
infos[*count].m_ctime = st.st_mtime;
infos[*count].i_node = st.st_ino;
mode_to_letters(st.st_mode, infos[*count].permission);
infos[*count].link_num = st.st_nlink;
infos[*count].block=st.st_blocks;
}
else
{
perror("stat");
continue;
}
(*count)++;
}
closedir(dir);//一定要记得关闭文件
}

由于需要将信息进行存储,所以ls_a需要接收结构体数组,count 用于记录文件个数.

opendir

opendir()函数用于打开一个目录,并返回指向该目录的句柄,供后续操作使用。

1
2
3
#include <dirent.h>
DIR * opendir(const char * dirpath);
Returns directory stream handlc,or NULL on error

readdir

readdir()函数从一个目录流中读取连续的条目.

1
2
3
#include <dirent.h>
struct dirent *readdir(DIR * dirp);
Returns pointer to a statically allocated structure describing next directory entry,or NULL on end-of-directory or error

每调用 readdir()一次,就会从 dirp 所指代的目录流中读取下一目录条目,并返回一枚指针, 指向经静态分配而得的 dirent 类型结构,内含与该条目相关的如下信息: 每次调用 readdir()都会覆盖该结构。

closedir

closedir()函数用于关闭处于打开状态的目录,同时释放它所使用的资源.(一定要记得关闭文件,否则就会造成内存泄漏).

1
int closedir(DIR *dirp);

stat (获取文件信息)

1
2
3
4
5
#include <sys/types.h> 
#include <sys/stat.h>
#include <unistd.h>

int stat(const char *pathname, struct stat *buf);

函数参数及返回值含义如下:

  • pathname:用于指定一个需要查看属性的文件路径。
  • buf:struct stat 类型指针,用于指向一个 struct stat 结构体变量。调用 stat 函数的时候需要传入一个 struct stat 变量的指针,获取到的文件属性信息就记录在 struct stat 结构体中。
  • 返回值:成功返回 0;失败返回-1,并设置 error。

struct stat

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct stat 
{
dev_t st_dev; /* 文件所在设备的 ID */
ino_t st_ino; /* 文件对应 inode 节点编号 */
mode_t st_mode; /* 文件对应的模式 */
nlink_t st_nlink; /* 文件的链接数 */
uid_t st_uid; /* 文件所有者的用户 ID */
gid_t st_gid; /* 文件所有者的组 ID */
dev_t st_rdev; /* 设备号(指针对设备文件) */
off_t st_size; /* 文件大小(以字节为单位) */
blksize_t st_blksize; /* 文件内容存储的块大小 */
blkcnt_t st_blocks; /* 文件内容所占块数 */
struct timespec st_atim; /* 文件最后被访问的时间 */
struct timespec st_mtim; /* 文件内容最后被修改的时间 */
struct timespec st_ctim; /* 文件状态最后被改变的时间 */
};

snprintf

snprintf 是一个 C 语言标准库函数,用于格式化字符串并将结果存储到一个字符数组中

1
int snprintf(char *str, size_t size, const char *format, ...);
  • tr: 指向存储结果的字符数组的指针。
  • size: 最大允许写入的字符数(包括字符串终止符)。
  • format: 格式化字符串,包含了要被写入到字符串中的文本和格式说明符。
  • ...: 可变数量的参数,用于替换格式字符串中的格式说明符。

snprintf 的行为类似于 printf,但是它会限制输出字符的数量,以防止溢出缓冲区。如果成功,snprintf 返回写入字符的总数(不包括字符串终止符),如果输出被截断,它会返回实际尝试写入的字符数。如果发生错误,返回负值。

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>
#include <unistd.h>
#include <locale.h>
#include <ctype.h>
#include <sys/ioctl.h>
#include <stdbool.h>
#include <getopt.h>
#include <error.h>

#define MAX_PATH 1024
#define MAX_FILE 512
#define MAX_RFILE 60000

enum order {
a, s, t, r, I, l, R
};

struct fileinfo
{
unsigned int i_node;
char permission[16];
short owner;
short group;
off_t size;
time_t c_ctime;
time_t m_ctime;
nlink_t link_num;
char name[MAX_PATH];
mode_t mode;
blkcnt_t block;
};

void do_open(char *path, int orders[]);
void ls_a(char *path, struct fileinfo *infos, int *count);
void only_ls(char *path, struct fileinfo *infos, int *count);
void mode_to_letters(mode_t mode, char modestr[]);
int cmp_name(const void *a, const void *b);
int cmp_time(const void *a, const void *b);
int cmp_size(const void *a, const void *b);
void show_orders(struct fileinfo *infos, int count, int orders[]);
char *uid_to_name(uid_t uid);
char *gid_to_name(gid_t gid);
void do_open_R(char *path, int orders[]);
void print(struct fileinfo infos);

int main(int argc, char *argv[])
{
int find = 0;
int orders[8] = {0};
for (int i = 1; i < argc; i++)
{
if (argv[i][0] == '-')
{
while ((find = getopt(argc, argv, "aIlrstR")) != -1)
{
switch (find)
{
case 'a':
orders[a] = 1;
break;
case 'r':
orders[r] = 1;
break;
case 's':
orders[s] = 1;
break;
case 't':
orders[t] = 1;
break;
case 'l':
orders[l] = 1;
break;
case 'R':
orders[R] = 1;
break;
case 'I':
orders[I] = 1;
break;
default:
fprintf(stderr, "Invalid option\n");
exit(EXIT_FAILURE);
}
}
}
}

int countfile = 0;
for (int i = 1; i < argc; i++)
{
if (argv[i][0] != '-')
{
countfile++;
}
}

if (countfile == 0)
{
char current_path[MAX_PATH];
if ((getcwd(current_path, MAX_PATH)) != NULL)
{
if (orders[R] == 0)
{
do_open(current_path, orders);
}
else
{
do_open_R(current_path, orders);
}
}
else
{
perror("getcwd");
exit(EXIT_FAILURE);
}
}

for (int i = 1; i < argc; i++)
{
if (argv[i][0] != '-')
{
if (countfile > 1)
{
printf("%s\n", argv[i]);
}
if (orders[R] == 0)
{
do_open(argv[i], orders);
}
else
{
do_open_R(argv[i],orders);
}
}
}
}

void do_open(char *path, int orders[])
{
int count = 0;
struct fileinfo infos[MAX_FILE];
if (orders[a])
{
ls_a(path, infos, &count);
}
if (orders[a] == 0)
{
only_ls(path, infos, &count);
}
if (orders[t])
{
qsort(infos, count, sizeof(struct fileinfo), cmp_time);
}
if ((orders[s] == 1) && (orders[t] == 0))
{
qsort(infos, count, sizeof(struct fileinfo), cmp_size);
}
if ((orders[s] == 0) && orders[t] == 0)
{
qsort(infos, count, sizeof(struct fileinfo), cmp_name);
}
show_orders(infos, count, orders);
}

void do_open_R(char *path, int orders[])
{
int count = 0;
struct fileinfo *infos = (struct fileinfo *)malloc(sizeof(struct fileinfo) * MAX_RFILE);
if (infos == NULL)
{
perror("malloc");
exit(EXIT_FAILURE);
}
if (orders[a])
{
ls_a(path, infos, &count);
}
if (orders[a] == 0)
{
only_ls(path, infos, &count);
}
if (orders[t])
{
qsort(infos, count, sizeof(struct fileinfo), cmp_time);
}
if ((orders[s] == 1) && (orders[t] == 0))
{
qsort(infos, count, sizeof(struct fileinfo), cmp_size);
}
if ((orders[s] == 0) && orders[t] == 0)
{
qsort(infos, count, sizeof(struct fileinfo), cmp_name);
}
show_orders(infos, count, orders);
for (int i = 0; i < count; i++)
{
if (S_ISDIR(infos[i].mode) && strcmp(infos[i].name, ".") != 0 && strcmp(infos[i].name, "..") != 0)
{
char sub_path[MAX_PATH];
snprintf(sub_path, sizeof(sub_path), "%s/%s", path, infos[i].name);
struct stat flag;
if (lstat(sub_path, &flag) == -1)
{
fprintf(stderr, "Permission denied or error reading: %s\n", sub_path);
continue;
}
if(S_ISLNK(flag.st_mode))
{
continue;
}
do_open_R(sub_path,orders);
}
}
free(infos);
}


void ls_a(char *path, struct fileinfo *infos, int *count)
{
DIR *dir;
struct dirent *dirp;
if ((dir = opendir(path)) == NULL)
{
printf("%s\n", path);
return ;
}
*count = 0;
struct stat st;
while ((dirp = readdir(dir)) != NULL)
{
char current_path[MAX_PATH];
snprintf(current_path, sizeof(current_path), "%s/%s", path, dirp->d_name);

if ((stat(current_path, &st)) != -1)
{

strncpy(infos[*count].name, dirp->d_name, sizeof(infos[*count].name) - 1);
infos[*count].name[sizeof(infos[*count].name) - 1] = '\0'; // Null-terminate the string
infos[*count].group = st.st_gid;
infos[*count].owner = st.st_uid;
infos[*count].mode = st.st_mode;
infos[*count].c_ctime = st.st_ctime;
infos[*count].size = st.st_size;
infos[*count].m_ctime = st.st_mtime;
infos[*count].i_node = st.st_ino;
mode_to_letters(st.st_mode, infos[*count].permission);
infos[*count].link_num = st.st_nlink;
infos[*count].block=st.st_blocks;
}
else
{
perror("stat");
continue;
}
(*count)++;
}
closedir(dir);//一定要记得关闭文件
}


void only_ls(char *path, struct fileinfo *infos, int *count)
{
DIR *dir;
struct dirent *dirp;
if ((dir = opendir(path)) == NULL)
{
printf("%s\n", path);
return ;
}
*count = 0;
struct stat st;
while ((dirp = readdir(dir)) != NULL)
{
if (dirp->d_name[0] != '.')
{
char current_path[MAX_PATH];
snprintf(current_path, sizeof(current_path), "%s/%s", path, dirp->d_name);
if ((stat(current_path, &st)) != -1)
{
snprintf(infos[*count].name, sizeof(infos[*count].name), "%s", dirp->d_name);
infos[*count].group = st.st_gid;
infos[*count].owner = st.st_uid;
infos[*count].mode = st.st_mode;
infos[*count].c_ctime = st.st_ctime;
infos[*count].size = st.st_size;
infos[*count].m_ctime = st.st_mtime;
infos[*count].i_node = st.st_ino;
mode_to_letters(st.st_mode, infos[*count].permission);
infos[*count].link_num = st.st_nlink;
infos[*count].block = st.st_blocks;
}
(*count)++;
}
}
closedir(dir);//一定要记得及时释放
}

void mode_to_letters(mode_t mode, char modestr[]) {
strcpy(modestr, "----------");
if (S_ISDIR(mode)) modestr[0] = 'd';
if (S_ISCHR(mode)) modestr[0] = 'c';
if (S_ISBLK(mode)) modestr[0] = 'b';

if (mode & S_IRUSR) modestr[1] = 'r';
if (mode & S_IWUSR) modestr[2] = 'w';
if (mode & S_IXUSR) modestr[3] = 'x';

if (mode & S_IRGRP) modestr[4] = 'r';
if (mode & S_IWGRP) modestr[5] = 'w';
if (mode & S_IXGRP) modestr[6] = 'x';

if (mode & S_IROTH) modestr[7] = 'r';
if (mode & S_IWOTH) modestr[8] = 'w';
if (mode & S_IXOTH) modestr[9] = 'x';
}

int cmp_name(const void *a, const void *b)
{
const char *name_a = ((struct fileinfo *)a)->name;
const char *name_b = ((struct fileinfo *)b)->name;
setlocale(LC_COLLATE, "");
return strcoll(name_a, name_b);
}

int cmp_size(const void *a, const void *b)
{
off_t size_a = ((struct fileinfo *)a)->size;
off_t size_b = ((struct fileinfo *)b)->size;
return size_a > size_b ? (size_a < size_b ? -1 : 0) : 1;
}

int cmp_time(const void *a, const void *b)
{
time_t time_a = ((struct fileinfo *)a)->m_ctime;
time_t time_b = ((struct fileinfo *)b)->m_ctime;
return time_a > time_b ? (time_a < time_b ? -1 : 0) : 1;
}

void show_orders(struct fileinfo *infos, int count, int orders[])
{
long long total = 0;
if(orders[l])
{
for(int i = 0; i < count; i++)
{
total+=infos[i].block/2;
}
printf("总计 : %-11ld\n", total);
}
if (orders[r])
{
for (int i = count - 1; i >= 0; i--)
{
if (orders[I])
{
printf("%-8d", infos[i].i_node);
}
if(orders[s])
{
printf("%-8d", infos[i].block/2);
}
if (orders[l])
{
printf("%s ", infos[i].permission);
printf("%4d ", (int)infos[i].link_num);
printf("%-8s ", uid_to_name(infos[i].owner));
printf("%-8s ", gid_to_name(infos[i].group));
printf("%8ld ", infos[i].size);
printf("%.12s ", ctime(&infos[i].c_ctime) + 4);
}
print(infos[i]);
}
}
else
{
for (int i = 0; i < count; i++)
{
if (orders[I])
{
printf("%-8d", infos[i].i_node);
}
if(orders[s])
{
printf("%-8d", infos[i].block/2);
}
if (orders[l])
{
printf("%s ", infos[i].permission);
printf("%4d ", (int)infos[i].link_num);
printf("%-8s ", uid_to_name(infos[i].owner));
printf("%-8s ", gid_to_name(infos[i].group));
printf("%8ld ", infos[i].size);
printf("%.12s ", ctime(&infos[i].c_ctime) + 4);
}
print(infos[i]);
}
}
}

char *uid_to_name(uid_t uid)
{
struct passwd *pw_ptr;
pw_ptr = getpwuid(uid);
if (pw_ptr == NULL)
{
static char numstr[10];
snprintf(numstr, sizeof(numstr), "%d", uid);
return numstr;
}
else
{
return pw_ptr->pw_name;
}
}

char *gid_to_name(gid_t gid)
{
struct group *getgrgid(), *grp_ptr;
static char numstr[10];
if ((grp_ptr = getgrgid(gid)) == NULL)
{
sprintf(numstr, "%d", gid);
return numstr;
}
else
{
return grp_ptr->gr_name;
}
}

void print(struct fileinfo infos)
{
if (S_ISREG(infos.mode))
{
// Regular file
if (strstr(infos.name, ".c") != NULL)
{
// .c file, print in a different color
printf("\033[40;32m %s\033[0m\n", infos.name);
}
else if (strstr(infos.name, ".out") != NULL)
{
// .out file, print in another color
printf("\033[40;33m %s\033[0m\n", infos.name);
}
else
{
// Other regular file
printf("%s\n", infos.name);
}
}
else if (S_ISDIR(infos.mode))
{
// Directory
printf("\033[40;34m %s\033[0m\n", infos.name);
}
else
{
// Other file types
printf("%s\n", infos.name);
}
}


malloc&free

malloc(0)返回一个有效的空间长度为0的内存首地址,但是没法用(只能进行申请和释放).

动态申请数组指针

1
2
int (*P)[3]=(int(*)[3])=malloc(sizeof(int)*3);
int (*q)[2][3]=(int(*)[2][3])malloc(sizeof(int)*6);

初始化

malloc函数分配得到的内存是未初始化的.一般在使用该内存空间时,要调用memset来初始化为0.

1
void* memset(void *dest,int c,size_t count);

该函数可以将指定的内存空间按字节单位置为指定的字符c。其中,dest为要清零的内存空间的首地址,c为要设定的值,count为被操作的内存空间的字节长度。

1
void* memcpy(void* dest, void* src, size_t count);

此函数也是按照字节进行拷贝的,dest指向目标地址的指针,也就是要被赋值的空间首地址;src指向源地址的指针,也就是要被复制的空间的首地址;count跟memset()一样表示被拷贝的字节数;返回值也是被赋值的目标地址的指针。

其他申请方式

calloc

1
void* calloc(size_t num, size_t size); 

realloc

1
void* realloc(void* memblock, size_t size);  // 为已分配的内存空间重新申请内存块

_msize

1
size_t _msize(void* memblock);  // Windows平台下专用函数,非C语言标准函数

返回malloc() & calloc() & realloc()等申请内存块的大小,参数是分配内存块的首地址,也就是malloc() & calloc() & realloc()等的返回值。

free

malloc()申请一块内存空间,OS会有一张表记录所申请空间的首地址和这块地址的长度,free(空间首地址),free会从表中查找到这块首地址对应的内存大小,一并释放掉。

  1. free()不能去释放栈区的空间,栈区空间是由OS管理的,由OS进行申请和释放。
  2. 释放空间后,指针需要置空,避免成为野指针。
1
2
3
4
int* q = (int*)malloc(3);
free(q); // 会报错,int型指针一次操作 4Byte,这里只申请了 3Byte 相当去别人的地盘上拆东西,那肯定是不允许的
int* n = (int*)malloc(7); // 允许多申请,但是 int型指针一次只能操作 4Byte 多余的空间浪费了
free(n); // 释放时,从OS维护的表中查找到空间长度,会一并释放掉

new & delete

new

new 在申请基本类型空间时,主要会经历两个过程:

  1. 调用 operator new(size_t)operator new[] (size_t) 申请空间
  2. 进行强制类型转换
1
2
3
4
5
6
7
8
9
10
11
// ====== 申请单个空间 ======
type* p = new type;
// 执行上面这条语句实际的过程是下面的语句
void* tmp = operator new(sizeof(type)); // 调用 operator new(size_t) 申请空间
type* p = static_cast<type*>(tmp); // 进行强制类型转换

// ====== 申请数组空间 ======
type* q = new type[N];
// 执行上面这条语句实际的过程是下面的语句
void* tmp = operator new[](sizeof(type) * N); // 调用 operator new[](size_t) 申请空间
type* p = static_cast<type*>(tmp); // 进行强制类型转换

static_cast

是一种用于显示类型转换的强制转换运算符。是一个编译时运算符,用于在兼容类型之间执行转换.

1
2
3
static_cast<new_type>(expression)
int integerNumber = 42;
double doubleNumber = static_cast<double>(integerNumber);

new 在申请 object 空间时,主要会经历三个过程:

​1.调用 operator new(size_t)operator new[] (size_t) 申请空间

​2.进行强制类型转换

​3.调用类的构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
// ====== 申请单个object ======
classname* p = new classname;
// 执行上面的语句实际的过程是下面的条语句
void* tmp = operator new(sizeof(classname)); // 调用 operator new(size_t) 申请空间
classname* p = static_cast<classname*>(tmp); // 进行强制类型转换
p->classname::classname(); // 调用类的构造函数,用户不允许这样调用构造函数,如果用户想调用可以通过 定位(placement)new 运算符 的方式调用

// ====== 申请object数组空间 ======
classname* q = new classname[N];
// 执行上面的语句实际的过程是下面的条语句
void* tmp = operator new[](sizeof(classname) * N + 4); // 调用 operator new[](size_t) 申请空间
classname* q = static_cast<classname*>(tmp + 4); // 进行强制类型转换
q->classname::classname(); // 调用 N次 构造函数

注意

申请的空间大小为

1
sizeof(classname)*N+4;

前4个字节写入数组大小,最后调用N次构造函数。

这里为什么要写入数组大小呢?释放内存之前会调用每个对象的析构函数。但是编译器并不知道 q 实际所指对象的大小。如果没有储存数组大小,编译器如何知道该把p所指的内存分为几次来调用析构函数呢?

new[] 调用的是operator new[],计算出数组总大小之后调用operator new。值得一提的是,可以通过()初始化数组为零值,实例:

1
char* p = new char[32]();

等同于:

1
2
char *p = new char[32];
memset(p, 32, 0);

new的底层

operator new(size_t) 是系统提供的全局函数,其 底层是由 malloc 实现的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*
operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,尝试
执行空 间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。
*/
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
// try to allocate size bytes
void *p;
while ((p = malloc(size)) == 0)
if (_callnewh(size) == 0)//是否注册了新的处理程序,没有则继续循环试图分配空间.
{
// report no memory
// 如果申请内存失败了,这里会抛出bad_alloc 类型异常
static const std::bad_alloc nomem;
_RAISE(nomem);
}
return (p);
}

delete

delete 的过程与 new 很相似,会调用 operator delete(void*)operator delete[] (void*) 释放内存。

1
2
3
4
5
6
7
delete p;
// 执行上面的代码实际过程是下面的语句
operator delete(p); // 调用 operator delete(void*); 释放空间

delete[] q;
// 执行上面的代码实际过程是下面的语句
operator delete[](q); // 调用 operator delete[](q); 释放空间

delete 释放 object 空间

  1. 调用类的析构函数
  2. 调用 operator delete(void*)operator delete[] (void*) 释放内存
1
2
3
4
5
6
7
8
9
delete obj;
// 执行上面的语句实际过程是下面的语句
obj->~classname(); // 首先调用类的析构函数
operator delete(obj); // 调用 operator delete(void*); 释放 object 内存空间

delete[] obj1;
// 执行上面的语句实际过程是下面的语句
obj->~classname(); // 调用 N次 类的析构函数
operator delete[](obj1); // 调用 operator delete[](void*); 释放 object 内存空间

new[]分配的内存只能由delete[]释放。如果由delete释放会崩溃,假设指针obj1指向new[]分配的内存,因为要4字节存储数组大小,实际分配的内存地址为obj1-4,系统记录的也是这个地址。delete[] 实际释放的就是obj1-4指向的内存。而delete会直接释放obj1指向的内存,这个内存根本没有被系统记录,所以会崩溃。

delete底层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/*
operator delete: 该函数最终是通过free来释放空间的
*/
void operator delete(void *pUserData)
{
_CrtMemBlockHeader * pHead;

RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));

if (pUserData == NULL)
return;

_mlock(_HEAP_LOCK); /* block other threads */
__TRY
/* get a pointer to memory block header */
pHead = pHdr(pUserData);

/* verify block type */
_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));

_free_dbg( pUserData, pHead->nBlockUse );

__FINALLY
_munlock(_HEAP_LOCK); /* release other threads */
__END_TRY_FINALLY

return;
}
/*
free的实现
*/
#define free(p) _free_dbg(p, _NORMAL_BLOCK)

总结

new一个数组时,与malloc相似,os会维护一张记录数组头指针和数组长度的表.

释放基本数据类型的指针时,数组的头指针最终会被free(q)释放,所以调用delete q或者delete[] q,最终的结果都是调用free(q);

释放自定义类型的数组时,如果类中有需要在析构函数中释放,直接调用delete obj只会调用一次析构函数,后执行free(),就没有调用其他的析构函数,会造成内存泄漏.一定调用delete[] obj释放内存

new 和 delete的重写和重载

这两个函数在使用时,其实执行的是全局的::operator new::operator delete,如果我们在类中重载了这两个函数,那么就会调用我们实现的,没有则使用全局的. 对于基本数据类型则使用全局的.

1
2
3
4
5
6
7
8
9
10
//全局运算符定义格式
void* operator new(size_t size [, param1, param2,....]);
void operator delete(void *p [, param1, param2, ...]);

//类内运算符定义格式
class CA
{
void* operator new(size_t size [, param1, param2,....]);
void operator delete(void *p [, param1, param2, ...]);
};

标准库提供的全局的 operator new( 函数 ) 有六种重载形式,operator delete也有六种重载形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// =======================new=========================//
void *operator new(std::size_t count)
throw(std::bad_alloc); // 一般的版本
void *operator new[](std::size_t count)
throw(std::bad_alloc);

void *operator new(std::size_t count, // 兼容早版本的 new
const std::nothrow_t const&) throw(); // 内存分配失败不会抛出异常
void *operator new[](std::size_t count,
const std::nothrow_t const&) throw();

void *operator new(std::size_t count, void *ptr) throw(); // placement 版本
void *operator new[](std::size_t count, void *ptr) throw();

// ======================delete========================//
void * operator delete(void *) noexcept;
void * operator delete[](void *) noexcept;

void * operator delete(void *, std::nothrow_t const&) noexcept;
void * operator delete[](void *, std::nothrow_t const&) noexcept;

void * operator delete(void *, void *) noexcept;
void * operator delete[](void *, void *) noexcept;

// VS C++下还有两个,GCC下不确定
void * operator delete(void *, size_t) noexcept;
void * operator delete[](void *, size_t) noexcept;

new/delete运算符重载规则:

  1. new和delete运算符重载必须成对出现。
  2. new运算符的第一个参数必须是size_t类型的,也就是指定分配内存的size尺寸;delete运算符的第一个参数必须是要销毁释放的内存对象。其他参数可以任意定义。
  3. 系统默认实现了new/delete、new[]/delete[]、 placement new 5个运算符(expression)。它们都有特定的意义。使用它们在底层调用对应的函数,可以是系统提供的,也可能是被重写或重载的。
  4. 你可以重写默认实现的全局运算符,比如你想对内存的分配策略进行自定义管理或者你想监测堆内存的分配情况或者你想做堆内存的内存泄露监控等。但是你重写的全局运算符一定要满足默认的规则定义。也可以重载全局运算符,但也必须符合默认的规则,即第一个参数不能变。
  5. 如果你想对某个类的堆内存分配的对象做特殊处理,那么你可以重载这个类的new/delete运算符。当重载这两个运算符时虽然没有带static属性,但是不管如何对类的new/delete运算符的重载总是被认为是静态成员函数。
  6. 当delete运算符的参数>=2个时,就需要自己负责对象析构函数的调用,并且以运算符函数的形式来调用delete运算符。
  7. 这里的重载遵循作用域覆盖原则,即在里向外寻找operator new的重载时,只要找到operator new()函数就不再向外查找,如果参数符合则通过,如果参数不符合则报错,而不管全局是否还有相匹配的函数原型。比如如果这里只将Foo中operator new(size_t, const std::nothrow_t&)删除掉,就会在61行报错:

对象的自动删除

一般来说系统对new/delete的默认实现就能满足我们的需求,我们不需要再去重载这两个运算符。那为什么C++还提供对这两个运算符的重载支持呢?答案还是在运算符本身具有的缺陷所致。我们知道用new关键字来创建堆内存对象是分为了2步:1.是堆内存分配,2.是对象构造函数的调用。而这两步中的任何一步都有可能会产生异常。如果说是在第一步出现了问题导致内存分配失败则不会调用构造函数,这是没有问题的。如果说是在第二步构造函数执行过程中出现了异常而导致无法正常构造完成,那么就应该要将第一步中所分配的堆内存进行销毁。C++中规定如果一个对象无法完全构造那么这个对象将是一个无效对象,也不会调用析构函数。为了保证对象的完整性,当通过new分配的堆内存对象在构造函数执行过程中出现异常时就会停止构造函数的执行并且自动调用对应的delete运算符来对已经分配的堆内存执行销毁处理,这就是所谓的对象的自动删除技术。正是因为有了对象的自动删除技术才能解决对象构造不完整时会造成内存泄露的问题。

全局delete运算符函数所支持的对象的自动删除技术虽然能解决对象本身的内存泄露问题,但是却不能解决对象构造函数内部的数据成员的内存分配泄露问题,此时我们需要将析构函数中需要释放的数据成员的内存在重载的operator delete函数中进行释放。

operator new运用技巧

  • 用于调试
  • 内存池优化
  • STL中的new

new和malloc的区别

内存的申请所在位置

new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。

那么自由存储区是否能够是堆(问题等价于new是否能在堆上动态分配内存),这取决于operator new 的实现细节。自由存储区不仅可以是堆,还可以是静态存储区,这都看operator new在哪里为对象分配内存。

特别的,new甚至可以不为对象分配内存!定位new的功能可以办到这一点:

1
new(place_address) type

place_address为一个指针,代表一块内存的地址。

返回类型安全性

new返回对象类型的指针,类型严格匹配

而malloc返回void *,需要通过强制类型转换将void *转换成我们需要的类型.

内存失败时的返回值

new内存分配失败时,会抛出bac_alloc异常,它不会返回NULL;malloc分配内存失败时返回NULL。

是否需要指定内存大小

使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算,而malloc则需要显式地指出所需内存的尺寸。

是否调用构造函数/析构函数

使用new操作符来分配对象内存时会经历三个步骤:

  1. 第一步:调用operator new 函数(对于数组是operator new[])分配一块足够大的,原始的,未命名的内存空间以便存储特定类型的对象。
  2. 第二步:编译器运行相应的构造函数以构造对象,并为其传入初值。
  3. 第三部:对象构造完成后,返回一个指向该对象的指针。

使用delete操作符来释放对象内存时会经历两个步骤:

  1. 第一步:调用对象的析构函数。
  2. 第二步:编译器调用operator delete(或operator delete[])函数释放内存空间。

对数组的处理

C++提供了new[]与delete[]来专门处理数组类型,new[]分配的内存必须使用delete[]进行释放。

new对数组的支持体现在它会分别调用构造函数函数初始化每一个数组元素,释放对象时为每个对象调用析构函数。注意delete[]要与new[]配套使用,不然会找出数组对象部分释放的现象,造成内存泄漏。

至于malloc,它并知道你在这块内存上要放的数组还是啥别的东西,反正它就给你一块原始的内存,在给你个内存的地址就完事。所以如果要动态分配一个数组的内存,还需要我们手动自定数组的大小:

1
int * ptr = (int *) malloc( sizeof(int)* 10 ); //分配一个10个int元素的数组

new与malloc是否可以相互调用

operator new 的实现可以基于malloc,而malloc的实现不可以去调用new。

是否可以被重载

opeartor new /operator delete可以被重载。标准库是定义了operator new函数和operator delete函数的8个重载版本:

// 这些版本可能抛出异常

1
2
3
4
void * operator new(size_t);
void * operator new[](size_t);
void * operator delete(void *) noexcept;
void * operator delete[](void *) noexcept;
1
2
3
4
5
// 这些版本承诺不抛出异常
void * operator new(size_t, nothrow_t const&) noexcept;
void * operator new[](size_t, nothrow_t const&) noexcept;
void * operator delete(void *, nothrow_t const&) noexcept;
void * operator delete[](void *, nothrow_t const&) noexcept
1
2
3
// VS C++下还有两个,GCC下不确定
void * operator delete(void *, size_t) noexcept;
void * operator delete[](void *, size_t) noexcept;

我们可以重载上面函数版本中的任意一个,前提是自定义版本必须位于全局作用域或者类作用域中。总之,我们有足够的自由去重载operator new /operator delete ,以决定我们的new与delete如何为对象分配内存,如何回收对象。

malloc/free并不允许重载。

能够直观地重新分配内存

使用malloc分配的内存后,如果在使用过程中发现内存不足,可以使用realloc函数进行内存重新分配实现内存的扩充。realloc先判断当前的指针所指内存是否有足够的连续空间,如果有,原地扩大可分配的内存地址,并且返回原来的地址指针;如果空间不够,先按照新指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,而后释放原来的内存区域。

new没有这样直观的配套设施来扩充内存。

客户处理内存分配不足

在operator new抛出异常以反映一个未获得满足的需求之前,它会先调用一个用户指定的错误处理函数,这就是new_handler。new_handler是一个指针类型:

1
2
3
4
namespace std
{
typedef void (*new_handler)();
}

指向了一个没有参数没有返回值的函数,即为错误处理函数。为了指定错误处理函数,客户需要调用set_new_handler,这是一个声明于的一个标准库函数:

1
2
3
4
namespace std
{
new_handler set_new_handler(new_handler p ) throw();
}

set_new_handler的参数为new_handler指针,指向了operator new 无法分配足够内存时该调用的函数。其返回值也是个指针,指向set_new_handler被调用前正在执行(但马上就要发生替换)的那个new_handler函数。

对于malloc,客户并不能够去编程决定内存不足以分配时要干什么事,只能看着malloc返回NULL。

Saturday, 2 March 2024

KDE got accepted as a mentoring organization for Google Summer of Code 2024! Are you thinking about getting involved into KDE development? Check out the cool ideas KDE devs came up, they showcase what can be achieved by taking part as a student in GSoC. How to start? How to get involved? How to make an impression that will help your application?

Google Summer of Code

Prerequisites

  • You like KDE, you like us as a community, you can follow our philosophy, you like our product (a desktop or at least an specific application), and you resonate with our tech stack (C++, Qt, CMake).
  • Grab some code from our GitLab, clone a repository and build it locally. This sounds easy. For first-timers it is not easy. Reach out for help in case you struggle.
  • Run your self-built software. Now you can explore the joy of developing KDE.

You do not need an idea, at least not yet. Give it some time.

Get involved

Try getting involved. Usually it is not easy to fix bugs of implement a feature request from KDE bugtracking system. Some are hard to fix. Others need debates of future directions or best ways to get things done.

I propose you start looking for other opportunities:

  1. Fix compiler warnings. Compilers analyze the code and as a result they might warn you. This can have various reasons like bad coding practice, code that is difficult to read and might easily be misread by humans, code with bad performance, bug-prone constructs.
  2. Fix deprecation warnings. KDE, Qt, and every software evolves. Old interfaces are replaced by newer ones. The old ones are not directly thrown away, but deprecated. The deprecation warning reminds the developer to migrate from the old to the new interface. Some deprecations are trivial to fix, others require ample code changes.
  3. Fix findings of static analyzers and linters. These are tools that analyze code more thorough compared to a compiler for the price of a longer runtime. They offer great hints for but are prone to false-positives (wrong warnings). Good tools for KDE are Cppcheck, Clazy and qmllint.
  4. Fix crash bugs. Crashes often occur when the code contains memory issues. Examples are using objects that were deleted, accessing arrays out of bounds, de-referencing null pointers. Tools like a good debugger, Valgrind, and AddressSanitizer (and its cousins MemorySanitizer and UndefinedBehaviorSanitizer) help to localize the problem. Crashes are more difficult to understand and fix compared to warnings for tools.

Try to work for fixes of one to ten cases. More makes reviewing harder. Create a pull request and wait for feedback.

Rationale

Why do I think these areas are good to start working? The maintainer might reject your pull request. This can always happen. Compared to implementing a whole new feature, the amount of work you invested is limited.

Once you have an idea for GSoC and write your application, you can point to your pull requests as proof of work. Maintainers see your involvement, they see how you interact, and you get an early sense whether you like the contributing experience or not.

Even in the case that you do not want to become an GSoC student, you improved KDE a tiny little bit. Great feeling, isn't it?

Word of warning

Not everybody deem warnings worth to be fixed -- in general or in specific cases. You will learn what kind of warnings getting fixed are welcome. I already wrote a blog post about my experiences with fixing some Cppcheck in Kile.

Thursday, 29 February 2024

It’s February already, and as expected I didn’t have too much time on my hands this month. I have some exciting related news though:

KDE Megarelease 6

If you somehow haven’t heard, a bunch of KDE 6-related stuff released yesterday! I highly recommend looking at the very nice announcement Carl and the rest of the KDE Promo team put together, it looks so good. Some of my changes in KCMs, Kiten, UnifiedPush support in Tokodon & NeoChat, PlasmaTube, and Tokodon are showcased there 🤩 One of my favorite changes is probably in KWin and the Wayland session, it’s near-perfect on my machine now.

NLnet Grant

For a while now, people have been asking about funding work on important Wayland features for artists and people who depend on accessibility features. We have been turning down offers from individual fundraisers, because behind the scenes my employer arranged a grant us to work on this task from NLnet! “Us” here means me and Nicolas Fella. I hope to begin in early April.

Tokodon

[Feature] I changed the thread visuals to improve the readability of replies. Note that it’s only one level deep, but even with that limitation I find it to be way better than it was before! [24.05]

Better thread visuals

[Feature] I did some neat notification changes, such as enabling group notifications by default and hiding the post actions unless required for notifications. The notifications page should have less visual noise now. [24.02.1]

Less visual noise on the notifications page

[Feature] Tokodon now warns if replies may be hidden from your server and suggests to open the original post in your web browser. This isn’t the greatest solution yet, eventually I want a way to load the post on the original server within Tokodon. This is a good start though, instead of showing nothing. [24.05]

The new replies message

NeoChat

[Bugfix] Rebased and cleaned up my bugfix to prevent editing messages destroying the formatting. This should be integrated soon!

Frameworks

[Bugfix] Fixed my typo that caused KFileMetadata to fail when not building with KArchive support, oops! [6.1]

[Bugfix] Add the missing and new Breeze icons dependency to kiconthemes. [6.1]

Like or comment on this post

Wednesday, 28 February 2024

There are release parties around the world, the UK has a party in Cambridge.

Action plan is to go to The Haymakers, High Street, Cambridge and get pizza, drinks and more pizza. at 19:00 Thursday the 29th Feb.

Look for the guy in the blue KDE T-shirt.

Sign up at https://community.kde.org/Promo/Events/Parties/KDE_6th_Megarelease#Cambridge

Monday, 26 February 2024

Today, we bring you a report on the brand-new release of the Maui Project.

We are excited to announce the latest release of MauiKit version 3.1.0, our comprehensive user interface toolkit specifically designed for convergent interfaces, the complying frameworks, an in-house developed set of convergent applications, and the upcoming convergent shell environment for desktop and mobile devices.

Built on the foundations of Qt Quick Controls, QML, and the power and stability of C++, MauiKit empowers developers to create adaptable and seamless user interfaces across a range of devices, and with this release, we are a step closer to finalizing the migration to a new major version – the upcoming MauiKit4 release.

Join us on this journey as we unveil the potential of MauiKit3 for building convergent interfaces, the roadmap towards MauiKit4 and its new additions, and finally discover the possibilities offered by the enhanced Maui App stack.

Community

To follow the Maui Project’s development or to just say hi, you can join us on Telegram @mauiproject

We are present on Twitter and Mastodon:

Thanks to the KDE contributors who have helped to translate the Maui Apps and Frameworks!

Downloads & Sources

You can get the stable release packages [APKs, AppImage, TARs] directly from the KDE downloads server at https://download.kde.org/stable/maui/

All of the Maui repositories have the newly released branches and tags. You can get the sources right from the Maui group: https://invent.kde.org/maui

MauiKit4 & Documentation

MauiKit Calendar, Accounts, and Image Tools have now been ported to Qt6, joining MauiMan, MauiKit, and File Browsing frameworks. Alongside with the porting efforts, the frameworks are now documented, and available on the KDE API documentation site [HERE]. The remaining frameworks to be ported to Qt6 are MauiKit Terminal, Documents, and Text Editor, which should be fully ported and documented by the upcoming release in May.

The porting also involved updating the framework CMake code to the latest ECM changes introduced for creating QML plugins.

For the upcoming porting of MauiKit Text Editor, there are plans to move to a different backend for rendering the text more efficiently, and for Documents, the comics and ebooks backends will be reviewed and refactored to address crashing issues under Android.

You can find more information about the roadmap and plans for the migration to Qt6 at: https://invent.kde.org/maui/mauikit/-/issues/35

MauiKit Frameworks & Apps

A quick overview of the issues addressed and updates are covered in the following list:

  • Updated translations and support for more languages
  • Rebase MauiKit ApplicationWindow to QQC2 ApplicationWindow to resolve focus-stealing issues in Android
  • Update all the applications’ source code syntax in QML for the upcoming Qt6 port
  • Pick up the system light/dark preference for Android in MauiKit3, and move the handling of the Android status bar color from each app to MauiKit’s ApplicationWindow control. For MauiKit4 relay in the QStyleHints new properties
  • Split the  MauiApp code from the CSDControls. Thus register CSDControls as a new attached property: Maui.CSD [link to docs]
  • Expose the MauiKit Application root element via the attached property MauiApp, as Maui.App.rootComponent [link to docs]
  • Station fixes for handling the “terminate-session” shortcut and fixes to crashing issues when closing tabs, coming from MauiKit Terminal
  • The MauiKit’s PageLayout control has been backported from MauiKit4 to MauiKit3, and its implementation has gained new features, such as moving the collapsable elements to the footer. [link to docs]
  • Index app is now using MauiKit’s PageLayout control for splitting the toolbar actions for constrained spaces, the same as Pix
  • Pix fix the tags hot-reloading on new tags created
  • Fixes to nota syncing the terminal working directory
  • Vvave has gained a new option to quickly start a playlist in shuffle mode and a sleep timer: to stop playback and/or close the app after a certain time or end of the playlist
  • MauiKit’s AboutDialog has been revised and organized
  • MauiKit FileBrowsing control for creating a new file now picks the icon from the name extension
  • MauiKit Terminal and Text Editor now have a custom control exposing configurations for selecting custom color schemes
  • Index now has options to tweak the embedded terminal in Linux, such as custom color schemes, and reorganized setting entries for the terminal configurations
  • Nota now reuses the new Text Editor control for picking the color scheme and new options for  tweaking the embedded terminal
  • All of the apps now use an exported definition or the copyright notice
  • Fixes for all of the apps where the names of developers and other non-translatable strings were being marked as such
  • Fixed keyboard shortcuts for Station, Vvave, and other apps
  • Added style for the ComboBox component
  • MauiKit corrects the usage of singleton instances across threads for all the frameworks, which avoids the double instantiation from the CPP and QML side
  • Fixes to MauiKit TextEditor long press actions on touch screens
  • Fixes to style hints for the scrollbar policy coming from MauiMan
  • Fixes to Nota’s recent view selection mode
  • On mobile devices do not cache image or icon previews for the thumbnail delegates

[Known Bugs]

  • Nota crashes when opening a new file from the recent view under Android.
  • MauiKit Documents fails to open large comic books on Android, causing the app to crash due to the current design of using multiple threads for different pages. This is planned to be solved at MauiKitDocuments4 porting
  • MauiKit Text Editor TextArea has a flickering issue, which makes content jumpy on Android only. This is from upstream and should be solved on MauiKitTextEditor4 porting when moving to a different backend.
  • MauiKit FileBrowsing support for browsing SD cards on Android is currently disabled due to missing bindings to the “new” Android storage API

[What’s Next?]

For the next release, we plan to work on stabilizing the Maui Shell experience, by adding  XWayland support, and a few more things:

  • Finalize the porting of the MauiKit Frameworks to Qt6
  • Finalize and review the MauiKit documentation
  • Start the porting process of the Maui Apps to MauiKit4 AKA Qt6
  • Review MauiKit4 on Android
  • Review the migration of TextEditor to a new backend
  • Fixes to bugs on the Maui Apps stack
  • Update the Maui Apps features that are still pending

 

Maui Shell

For this release, Maui Shell and its components have received minimal updates coming from:

  • MauiCore and Maui Settings new modules for Audio and Network
  • Updated Maui Settings for MauiKit4 changes
  • Review shell aka Cask elements to be up to date with the MauiKit4 changes

 

That’s it for now. Until the next blog post, that will be a bit closer to the 3.1.1 stable release.

To follow the Maui Project’s development or say hi, you can join us on Telegram: https://t.me/mauiproject.

We are present on Twitter and Mastodon:

New release schedule

The post Maui Release Briefing #5 appeared first on MauiKit — #UIFramework.

Sunday, 25 February 2024

You may have already read about it on Volkers blog: we together with people from other public transport related projects are building a public transport routing service called Transitous. While of course our main motivation is to use it in KDE Itinerary, KDE’s travel planning app, it will be open for use in other apps.

We also have a little web interface running at transitous.org.

We are building this service based on great existing software, in particularly MOTIS.

Screenshot of the Transitous web interface, showing the positions of long-distance transit vehicles in Germany, the Netherlands, Switzerland, Latvia, Estonia and Sweden

Now, to make this really useful, we need data on more regions. Luckily, for most regions and countries that is fairly easy. Most transport agencies and countries make GTFS feeds available, that we can just use.

Adding an additional feed doesn’t take long and doesn’t need programming experience. It’s pretty much just creating a small text file that explains how and where to download the data from.

Those links don’t necessarily stay the same forever, so we would be happy if lots of people take care of their region, and update the link every few years. It is really little work if split up, but can’t all be handled by a small team.

To make it even easier, we can already use the Transitland Atlas feed collection, for which you just need to choose the feed to use. The url will then automatically be looked up.

You can find out how to add a feed here. Please let us know if the documentation is unclear anywhere.

If you are interested in using this service in your own application, it is probably a bit too early for production, but it makes sense to already implement support for the MOTIS API that we use. You can find an early version of our API documentation here.

If there is anything else you are interested in helping with, for example improving our ansible playbook, creating a website, improving MOTIS or working on integrating OpenStreetMap routing, you can find our open tasks here. We appreciate any help on those issues, and it of course speeds up the development of the project.

Plasma Pass is a Plasma applet for the Pass password manager

This release includes build fixes for Plasma 6, due to be released later this week.

URL: https://download.kde.org/stable/plasma-pass/
Sha256: 2a726455084d7806fe78bc8aa6222a44f328b6063479f8b7afc3692e18c397ce
Signed by E0A3EB202F8E57528E13E72FD7574483BB57B18D Jonathan Esk-Riddell <jr@jriddell.org>
https://jriddell.org/esk-riddell.gpg

Saturday, 24 February 2024

<!-- <div style="text-align: center;"> -->

<!-- # Adding Multi-Format Rendering to Kdenlive -->

sdfsdfdsfdsfsdf <sub>Source: <a target="_blank" href="https://dot.kde.org/sites/dot.kde.org/files/plasma-5.24-SoK.jpg" style="text-decoration: none; text-underline-offset: auto;">dot.kde.org</a></sub>

About Me

I am Ajay Chauhan (IRC: hisir:matrix.org), currently in my second year of undergraduate studies in Computer Science & Engineering. I'll be working on adding multi-format rendering to Kdenlive for my Season of KDE project. This post describes my journey with KDE and why I submitted this project for the Season of KDE.

My introduction to KDE and Kdenlive

I was first introduced to KDE when I was distrohopping, starting with Ubuntu GNOME, then Arch, and finally settling on KDE Neon so that I could experience KDE's latest features. Although it was a bit buggy at times, I enjoyed using the KDE Plasma so much that I've stuck with it for over two years now.

I also used the Qt framework to write GUI applications in C++ for school projects in the past. The Season of KDE will also allow me to get better acquainted with KDE development

What is Kdenlive?

Kdenlive is a free and open-source video editing software that is based on the MLT Framework. Kdenlive is an acronym for KDE Non-Linear Video Editor. It works on GNU/Linux, Windows, BSD and MacOS.
Whenever I've needed to edit videos, Kdenlive has always been my go-to video editor. I've used it to edit presentations for college and small projects.

I'll be working on adding Multi-format rendering (horizontal/vertical/square) to Kdenlive for my Season of KDE project. By adding support for rendering in horizontal, portrait, and square formats at export, videos can be rendered in different aspect ratios for different social media platforms and use cases easily.

My mentors for the project are Julius Künzel and Jean-Baptiste Mardelle.

The key tasks of my project are :

  • Add code to apply filters to the clips in the main tractor to achieve the desired aspect ratio.
  • Modify the user interface to allow users to select the desired aspect ratio during export and invoke that filter when they choose one of the aspect ratio
  • Ensure that the aspect ratio selected by the user is integrated into the final rendering profile

This will allow users to render videos tailored for different popular platforms for use cases like social media stories, square videos for Instragram post etc.

Work done so far

At first, I did the math on how to get the aspect ratio right. The core of the implementation is a calculateCropParameters function that calculates the correct cropping, given the source and destination aspect ratios. It determines whether the source video needs to be cropped on the sides for a wider aspect or on the top and bottom for a taller aspect ratio.

calculateCropParameters(sourceAspectRatio, targetAspectRatio, ...) {
  if (sourceAspectRatio differs from target) {
    if (source wider than target) {
       // crop sides
    } else {
       // crop top and bottom
    }
  } else {
    // no crop needed
  }
}

For this the first step in this process was to extract the properties of the source video: its width, height, and Display Aspect Ratio (DAR).

int sourceWidth = pCore->getCurrentProfile()->width();
int sourceHeight = pCore->getCurrentProfile()->height();
double sourceAspectRatio = pCore->getCurrentProfile()->dar();

The pCore->getCurrentProfile() function plays a main role here, providing access to the video's current profile settings, including the DAR. The DAR gives the width-to-height ratio of the video as displayed.

Setting Crop Parameters

After calculating the necessary crop dimensions, the final step was to apply these adjustments to the video. This was done by setting up a crop filter with the calculated parameters from calculateCropParameters.

std::unique_ptr<Mlt::Filter> cropFilter = std::make_unique<Mlt::Filter>(pCore->getProjectProfile(), "crop");
cropFilter->set("left", leftOffset);
cropFilter->set("right", sourceWidth - cropWidth - leftOffset);
cropFilter->set("top", topOffset);
cropFilter->set("bottom", sourceHeight - cropHeight - topOffset);

Demo video of work till now:

<div class="responsive-video-wrapper"> <iframe width="560" height="600" src="https://www.youtube.com/embed/oqrDxwQdwx0" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> </div>

Merge Request: Implement Multi-format Rendering↗

The main challenges faced in setting up a new profile were configuring the producer and consumer xmlConsumer2() to render video in the new aspect ratio profile when generating an XML playlist. Thanks to the mentor, who helped a lot with this task.

What's next?

In the upcoming weeks, I plan to add a GUI, modify the user interface to allow users to select the desired aspect ratio during export. And also ensure that I don't leave any bit and that it doesn't give any kind of error with the different aspect ratio of the source video that the user provides.

<style> .responsive-video-wrapper { position: relative; overflow: hidden; padding-top: 56.25%; /* 16:9 Aspect Ratio / border-radius: 10px; / Adjust this value for a subtle rounding effect */ }

.responsive-video-wrapper iframe { position: absolute; top: 0; left: 0; width: 100%; height: 100%; border-radius: 10px; /* Adjust this value for a subtle rounding effect */ } </style>

Friday, 23 February 2024

Just in time for KDE Plasma 6, Chromium 122 and Electron 29 have been released! They contain my patch that adds support for Wayland’s new cursor-shape-v1 extension protocol. When running natively in a Plasma 6 Wayland session, up-to-date Chromium-based browsers and Electron apps should now always use the correct mouse cursor theme and have the...

The post cursor-shape-v1 in Chromium and Electron first appeared on Ilya's Blog.